mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-01-31 23:32:16 +00:00
Simplify the handling of double spends somewhat.
This commit is contained in:
parent
608810cfc1
commit
76e539e8e7
@ -309,7 +309,7 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
* @return NO_SUCH_TX if transaction is not the prevtx, ALREADY_SPENT if there was a conflict, SUCCESS if not.
|
||||
*/
|
||||
public ConnectionResult connect(Transaction transaction, ConnectMode mode) {
|
||||
if (!transaction.getHash().equals(outpoint.getHash()))
|
||||
if (!transaction.getHash().equals(outpoint.getHash()) && mode != ConnectMode.DISCONNECT_ON_CONFLICT)
|
||||
return ConnectionResult.NO_SUCH_TX;
|
||||
checkElementIndex((int) outpoint.getIndex(), transaction.getOutputs().size(), "Corrupt transaction");
|
||||
TransactionOutput out = transaction.getOutput((int) outpoint.getIndex());
|
||||
@ -332,15 +332,21 @@ public class TransactionInput extends ChildMessage implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Release the connected output, making it spendable once again.
|
||||
* If this input is connected, check the output is connected back to this input and release it if so, making
|
||||
* it spendable once again.
|
||||
*
|
||||
* @return true if the disconnection took place, false if it was not connected.
|
||||
*/
|
||||
boolean disconnect() {
|
||||
if (outpoint.fromTx == null) return false;
|
||||
outpoint.fromTx.getOutputs().get((int) outpoint.getIndex()).markAsUnspent();
|
||||
outpoint.fromTx = null;
|
||||
return true;
|
||||
TransactionOutput output = outpoint.fromTx.getOutput((int) outpoint.getIndex());
|
||||
if (output.getSpentBy() == this) {
|
||||
output.markAsUnspent();
|
||||
outpoint.fromTx = null;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,7 +153,7 @@ public class TransactionOutPoint extends ChildMessage implements Serializable {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "outpoint " + hash.toString() + ":" + index;
|
||||
return hash.toString() + ":" + index;
|
||||
}
|
||||
|
||||
|
||||
|
@ -971,7 +971,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
* Checks if "tx" is spending any inputs of pending transactions. Not a general check, but it can work even if
|
||||
* the double spent inputs are not ours. Returns the pending tx that was double spent or null if none found.
|
||||
*/
|
||||
private Transaction findDoubleSpendAgainstPending(Transaction tx) {
|
||||
private TransactionInput findDoubleSpendAgainstPending(Transaction tx) {
|
||||
checkState(lock.isLocked());
|
||||
// Compile a set of outpoints that are spent by tx.
|
||||
HashSet<TransactionOutPoint> outpoints = new HashSet<TransactionOutPoint>();
|
||||
@ -983,7 +983,7 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
for (TransactionInput input : p.getInputs()) {
|
||||
if (outpoints.contains(input.getOutpoint())) {
|
||||
// It does, it's a double spend against the pending pool, which makes it relevant.
|
||||
return p;
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1236,17 +1236,11 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
addWalletTransaction(Pool.SPENT, tx);
|
||||
}
|
||||
|
||||
Transaction doubleSpend = findDoubleSpendAgainstPending(tx);
|
||||
if (doubleSpend != null) {
|
||||
TransactionInput doubleSpent = findDoubleSpendAgainstPending(tx);
|
||||
if (doubleSpent != null) {
|
||||
// This is mostly the same as the codepath in updateForSpends, but that one is only triggered when
|
||||
// the transaction being double spent is actually in our wallet (ie, maybe we're double spending).
|
||||
log.warn(" saw double spend from chain override pending tx {}", doubleSpend.getHashAsString());
|
||||
log.warn(" <-pending ->dead killed by {}", tx.getHashAsString());
|
||||
pending.remove(doubleSpend.getHash());
|
||||
addWalletTransaction(Pool.DEAD, doubleSpend);
|
||||
// Inform the event listeners of the newly dead tx.
|
||||
doubleSpend.getConfidence().setOverridingTransaction(tx);
|
||||
// TODO: Disconnect the inputs of doubleSpend here.
|
||||
killTx(tx, doubleSpent, doubleSpent.getParentTransaction());
|
||||
}
|
||||
}
|
||||
|
||||
@ -1286,42 +1280,15 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
}
|
||||
|
||||
if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
|
||||
// Double spend! Work backwards like so:
|
||||
//
|
||||
// A -> spent by B [pending]
|
||||
// \-> spent by C [this tx]
|
||||
//
|
||||
// fromTx here was set by the connect call above.
|
||||
Transaction doubleSpent = input.getOutpoint().fromTx; // == A
|
||||
checkNotNull(doubleSpent);
|
||||
int index = (int) input.getOutpoint().getIndex();
|
||||
TransactionOutput output = doubleSpent.getOutput(index);
|
||||
TransactionInput spentBy = checkNotNull(output.getSpentBy());
|
||||
Transaction connected = checkNotNull(spentBy.getParentTransaction());
|
||||
if (fromChain) {
|
||||
// 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).
|
||||
if (pending.containsKey(connected.getHash())) {
|
||||
log.warn("Saw double spend from chain override pending tx {}", connected.getHashAsString());
|
||||
log.warn(" <-pending ->dead killed by {}", tx.getHashAsString());
|
||||
pending.remove(connected.getHash());
|
||||
dead.put(connected.getHash(), connected);
|
||||
// Now forcibly change the connection.
|
||||
input.connect(unspent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT);
|
||||
// Inform the [tx] event listeners of the newly dead tx. This sets confidence type also.
|
||||
connected.getConfidence().setOverridingTransaction(tx);
|
||||
} else {
|
||||
throw new VerificationException("Transaction from chain double spent in unspent/spent maps: " + tx.getHashAsString()) ;
|
||||
}
|
||||
// This will be handled later by processTxFromBestChain.
|
||||
} else {
|
||||
// A pending transaction that tried to double spend our coins - we log and ignore it, because either
|
||||
// 1) The double-spent tx is confirmed and thus this tx has no effect .... or
|
||||
// 2) Both txns are pending, neither has priority. Miners will decide in a few minutes which won.
|
||||
// We saw two pending transactions that double spend each other. We don't know which will win.
|
||||
// Either that, or we somehow allowed ourselves to create double spends ourselves!
|
||||
// TODO: Find some way to communicate to the user that both transactions in jeopardy.
|
||||
log.warn("Saw double spend from another pending transaction, ignoring tx {}",
|
||||
tx.getHashAsString());
|
||||
tx.getHashAsString());
|
||||
log.warn(" offending input is input {}", tx.getInputs().indexOf(input));
|
||||
// TODO: We should report this state via tx confidence somehow ("in jeopardy"?)
|
||||
// Fall through now to checking the pending inputs.
|
||||
}
|
||||
} else if (result == TransactionInput.ConnectionResult.SUCCESS) {
|
||||
// Otherwise we saw a transaction spend our coins, but we didn't try and spend them ourselves yet.
|
||||
@ -1354,6 +1321,21 @@ public class Wallet implements Serializable, BlockChainListener {
|
||||
}
|
||||
}
|
||||
|
||||
private void killTx(Transaction overridingTx, TransactionInput overridingInput, Transaction killedTx) {
|
||||
TransactionOutPoint overriddenOutPoint = overridingInput.getOutpoint();
|
||||
log.warn("Saw double spend of {} from chain override pending tx {}",
|
||||
overriddenOutPoint, killedTx.getHashAsString());
|
||||
log.warn(" <-pending ->dead killed by {}", overridingTx.getHashAsString());
|
||||
checkState(overridingInput.connect(overridingTx, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT) == TransactionInput.ConnectionResult.SUCCESS);
|
||||
pending.remove(killedTx.getHash());
|
||||
addWalletTransaction(Pool.DEAD, killedTx);
|
||||
// Inform the [tx] event listeners of the newly dead tx and disconnect the other inputs.
|
||||
for (TransactionInput deadInput : killedTx.getInputs()) deadInput.disconnect();
|
||||
killedTx.getConfidence().setOverridingTransaction(overridingTx);
|
||||
// TODO: Move newly unspent transactions (if any) back into the unspent pool to avoid inconsistency.
|
||||
// TODO: Recursively kill other transactions that were double spent.
|
||||
}
|
||||
|
||||
/**
|
||||
* If the transactions outputs are all marked as spent, and it's in the unspent map, move it.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user