mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-11-01 21:17:13 +00: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:
@@ -204,6 +204,17 @@ public class Transaction extends Message implements Serializable {
|
|||||||
return null;
|
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
|
* 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.
|
* 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.
|
* there's no need to go through and do it again.
|
||||||
*/
|
*/
|
||||||
private void updateForSpends(Transaction tx) throws VerificationException {
|
private void updateForSpends(Transaction tx) throws VerificationException {
|
||||||
|
// tx is on the best chain by this point.
|
||||||
for (TransactionInput input : tx.inputs) {
|
for (TransactionInput input : tx.inputs) {
|
||||||
TransactionInput.ConnectionResult result = input.connect(unspent, false);
|
TransactionInput.ConnectionResult result = input.connect(unspent, false);
|
||||||
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
|
if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
|
||||||
// Doesn't spend any of our outputs or is coinbase.
|
// Not found in the unspent map. Try again with the spent map.
|
||||||
continue;
|
result = input.connect(spent, false);
|
||||||
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
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
|
// 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).
|
// 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
|
// 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.
|
// us to use. Move if not.
|
||||||
Transaction connected = input.outpoint.fromTx;
|
Transaction connected = input.outpoint.fromTx;
|
||||||
if (connected.getValueSentToMe(this, false).equals(BigInteger.ZERO)) {
|
maybeMoveTxToSpent(connected, "prevtx");
|
||||||
// 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);
|
/** 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.
|
// Mark the outputs of the used transcations as spent, so we don't try and spend it again.
|
||||||
for (TransactionInput input : tx.inputs) {
|
for (TransactionInput input : tx.inputs) {
|
||||||
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
|
||||||
|
Transaction connectedTx = connectedOutput.parentTransaction;
|
||||||
connectedOutput.markAsSpent(input);
|
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.
|
// Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
|
||||||
pending.put(tx.getHash(), tx);
|
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
|
* 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>
|
* 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);
|
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
assertEquals(v1, wallet.getBalance());
|
assertEquals(v1, wallet.getBalance());
|
||||||
|
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||||
|
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||||
|
|
||||||
ECKey k2 = new ECKey();
|
ECKey k2 = new ECKey();
|
||||||
BigInteger v2 = toNanoCoins(0, 50);
|
BigInteger v2 = toNanoCoins(0, 50);
|
||||||
Transaction t2 = wallet.createSend(k2.toAddress(params), v2);
|
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.
|
// Do some basic sanity checks.
|
||||||
assertEquals(1, t2.inputs.size());
|
assertEquals(1, t2.inputs.size());
|
||||||
assertEquals(myAddress, t2.inputs.get(0).getScriptSig().getFromAddress());
|
assertEquals(myAddress, t2.inputs.get(0).getScriptSig().getFromAddress());
|
||||||
|
|
||||||
// We have NOT proven that the signature is correct!
|
// 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
|
@Test
|
||||||
@@ -76,10 +85,14 @@ public class WalletTest {
|
|||||||
|
|
||||||
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
wallet.receive(t1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
||||||
assertEquals(v1, wallet.getBalance());
|
assertEquals(v1, wallet.getBalance());
|
||||||
|
assertEquals(1, wallet.getPoolSize(Wallet.Pool.UNSPENT));
|
||||||
|
assertEquals(1, wallet.getPoolSize(Wallet.Pool.ALL));
|
||||||
|
|
||||||
BigInteger v2 = toNanoCoins(0, 50);
|
BigInteger v2 = toNanoCoins(0, 50);
|
||||||
Transaction t2 = createFakeTx(params, v2, myAddress);
|
Transaction t2 = createFakeTx(params, v2, myAddress);
|
||||||
wallet.receive(t2, null, BlockChain.NewBlockType.SIDE_CHAIN);
|
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());
|
assertEquals(v1, wallet.getBalance());
|
||||||
}
|
}
|
||||||
@@ -207,6 +220,8 @@ public class WalletTest {
|
|||||||
wallet.receive(inbound1, null, BlockChain.NewBlockType.BEST_CHAIN);
|
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
|
// 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.
|
// 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);
|
Address someOtherGuy = new ECKey().toAddress(params);
|
||||||
Transaction outbound1 = wallet.createSend(someOtherGuy, coinHalf);
|
Transaction outbound1 = wallet.createSend(someOtherGuy, coinHalf);
|
||||||
wallet.confirmSend(outbound1);
|
wallet.confirmSend(outbound1);
|
||||||
|
|||||||
Reference in New Issue
Block a user