mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-03-13 11:36:15 +01:00
When confirming a transaction as sent, move connected newly spent transactions from unspent->spent. Introduce a method to do this, so as to avoid duplication with updateForSpends(). Add a getPoolSize() method and use it in unit tests to verify the pools at various points. Resolves issue 72.
This commit is contained in:
parent
91fe7cdefb
commit
9d5af32a9c
3 changed files with 74 additions and 10 deletions
|
@ -204,6 +204,17 @@ public class Transaction extends Message implements Serializable {
|
|||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if every output is marked as spent.
|
||||
*/
|
||||
public boolean isEveryOutputSpent() {
|
||||
for (TransactionOutput output : outputs) {
|
||||
if (output.isAvailableForSpending())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* These constants are a part of a scriptSig signature on the inputs. They define the details of how a
|
||||
* transaction can be redeemed, specifically, they control how the hash of the transaction is calculated.
|
||||
|
|
|
@ -330,12 +330,19 @@ public class Wallet implements Serializable {
|
|||
* there's no need to go through and do it again.
|
||||
*/
|
||||
private void updateForSpends(Transaction tx) throws VerificationException {
|
||||
// tx is on the best chain by this point.
|
||||
for (TransactionInput input : tx.inputs) {
|
||||
TransactionInput.ConnectionResult result = input.connect(unspent, false);
|
||||
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
|
||||
// Doesn't spend any of our outputs or is coinbase.
|
||||
continue;
|
||||
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
||||
// Not found in the unspent map. Try again with the spent map.
|
||||
result = input.connect(spent, false);
|
||||
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
|
||||
// Doesn't spend any of our outputs or is coinbase.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
||||
// Double spend! This must have overridden a pending tx, or the block is bad (contains transactions
|
||||
// that illegally double spend: should never occur if we are connected to an honest node).
|
||||
//
|
||||
|
@ -370,14 +377,21 @@ public class Wallet implements Serializable {
|
|||
// The outputs are already marked as spent by the connect call above, so check if there are any more for
|
||||
// us to use. Move if not.
|
||||
Transaction connected = input.outpoint.fromTx;
|
||||
if (connected.getValueSentToMe(this, false).equals(BigInteger.ZERO)) {
|
||||
// There's nothing left I can spend in this transaction.
|
||||
if (unspent.remove(connected.getHash()) != null) {
|
||||
log.info(" prevtx <-unspent");
|
||||
log.info(" prevtx ->spent");
|
||||
spent.put(connected.getHash(), connected);
|
||||
}
|
||||
maybeMoveTxToSpent(connected, "prevtx");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** If the transactions outputs are all marked as spent, and it's in the unspent map, move it. */
|
||||
private void maybeMoveTxToSpent(Transaction tx, String context) {
|
||||
if (tx.isEveryOutputSpent()) {
|
||||
// There's nothing left I can spend in this transaction.
|
||||
if (unspent.remove(tx.getHash()) != null) {
|
||||
if (log.isInfoEnabled()) {
|
||||
log.info(" " + context + " <-unspent");
|
||||
log.info(" " + context + " ->spent");
|
||||
}
|
||||
spent.put(tx.getHash(), tx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -411,12 +425,36 @@ public class Wallet implements Serializable {
|
|||
// Mark the outputs of the used transcations as spent, so we don't try and spend it again.
|
||||
for (TransactionInput input : tx.inputs) {
|
||||
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
||||
Transaction connectedTx = connectedOutput.parentTransaction;
|
||||
connectedOutput.markAsSpent(input);
|
||||
maybeMoveTxToSpent(connectedTx, "spent tx");
|
||||
}
|
||||
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
||||
pending.put(tx.getHash(), tx);
|
||||
}
|
||||
|
||||
// This is used only for unit testing, it's an internal API.
|
||||
enum Pool {
|
||||
UNSPENT,
|
||||
SPENT,
|
||||
PENDING,
|
||||
INACTIVE,
|
||||
DEAD,
|
||||
ALL,
|
||||
}
|
||||
|
||||
int getPoolSize(Pool pool) {
|
||||
switch (pool) {
|
||||
case UNSPENT: return unspent.size();
|
||||
case SPENT: return spent.size();
|
||||
case PENDING: return pending.size();
|
||||
case INACTIVE: return inactive.size();
|
||||
case DEAD: return dead.size();
|
||||
case ALL: return unspent.size() + spent.size() + pending.size() + inactive.size() + dead.size();
|
||||
}
|
||||
throw new RuntimeException("Unreachable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Statelessly creates a transaction that sends the given number of nanocoins to address. The change is sent to
|
||||
* the first address in the wallet, so you must have added at least one key.<p>
|
||||
|
|
|
@ -56,16 +56,25 @@ public class WalletTest {
|
|||
|
||||
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||
assertEquals(v1, wallet.getBalance());
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
|
||||
ECKey k2 = new ECKey();
|
||||
BigInteger v2 = toNanoCoins(0, 50);
|
||||
Transaction t2 = wallet.createSend(k2.toAddress(params), v2);
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
|
||||
// Do some basic sanity checks.
|
||||
assertEquals(1, t2.inputs.size());
|
||||
assertEquals(myAddress, t2.inputs.get(0).getScriptSig().getFromAddress());
|
||||
|
||||
// We have NOT proven that the signature is correct!
|
||||
|
||||
wallet.confirmSend(t2);
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.PENDING));
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.SPENT));
|
||||
assertEquals(2, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -76,10 +85,14 @@ public class WalletTest {
|
|||
|
||||
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||
assertEquals(v1, wallet.getBalance());
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
|
||||
BigInteger v2 = toNanoCoins(0, 50);
|
||||
Transaction t2 = createFakeTx(params, v2, myAddress);
|
||||
wallet.receive(t2, null, BlockChain.NewBlockType.SIDE_CHAIN);
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.INACTIVE));
|
||||
assertEquals(2, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
|
||||
assertEquals(v1, wallet.getBalance());
|
||||
}
|
||||
|
@ -207,6 +220,8 @@ public class WalletTest {
|
|||
wallet.receive(inbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||
// Send half to some other guy. Sending only half then waiting for a confirm is important to ensure the tx is
|
||||
// in the unspent pool, not pending or spent.
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||
Address someOtherGuy = new ECKey().toAddress(params);
|
||||
Transaction outbound1 = wallet.createSend(someOtherGuy, coinHalf);
|
||||
wallet.confirmSend(outbound1);
|
||||
|
|
Loading…
Add table
Reference in a new issue