/**
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.bitcoin.core;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import static com.google.bitcoin.core.Utils.bitcoinValueToFriendlyString;
/**
* A Wallet stores keys and a record of transactions that have not yet been spent. Thus, it is capable of
* providing transactions on demand that meet a given combined value.<p>
*
* The Wallet is read and written from disk, so be sure to follow the Java serialization versioning rules here. We
* use the built in Java serialization to avoid the need to pull in a potentially large (code-size) third party
* serialization library.<p>
*/
public class Wallet implements Serializable {
private static final Logger log = LoggerFactory.getLogger(Wallet.class);
private static final long serialVersionUID = 2L;
// Algorithm for movement of transactions between pools. Outbound tx = us spending coins. Inbound tx = us
// receiving coins. If a tx is both inbound and outbound (spend with change) it is considered outbound for the
// purposes of the explanation below.
//
// 1. Outbound tx is created by us: ->pending
// 2. Outbound tx that was broadcast is accepted into the main chain:
// <-pending and
// If there is a change output ->unspent
// If there is no change output ->spent
// 3. Outbound tx that was broadcast is accepted into a side chain:
// ->inactive (remains in pending).
// 4. Inbound tx is accepted into the best chain:
// ->unspent/spent
// 5. Inbound tx is accepted into a side chain:
// ->inactive
// Whilst it's also 'pending' in some sense, in that miners will probably try and incorporate it into the
// best chain, we don't mark it as such here. It'll eventually show up after a re-org.
// 6. Outbound tx that is pending shares inputs with a tx that appears in the main chain:
// <-pending ->dead
//
// Re-orgs:
// 1. Tx is present in old chain and not present in new chain
// <-unspent/spent ->pending
// These newly inactive transactions will (if they are relevant to us) eventually come back via receive()
// as miners resurrect them and re-include into the new best chain.
// 2. Tx is not present in old chain and is present in new chain
// <-inactive and ->unspent/spent
// 3. Tx is present in new chain and shares inputs with a pending transaction, including those that were resurrected
// due to point (1)
// <-pending ->dead
//
// Balance:
// 1. Sum up all unspent outputs of the transactions in unspent.
// 2. Subtract the inputs of transactions in pending.
// 3. If requested, re-add the outputs of pending transactions that are mine. This is the estimated balance.
/**
* Map of txhash->Transactions that have not made it into the best chain yet. They are eligible to move there but
* are waiting for a miner to send a block on the best chain including them. These transactions inputs count as
* spent for the purposes of calculating our balance but their outputs are not available for spending yet. This
* means after a spend, our balance can actually go down temporarily before going up again!
*/
public final Map<Sha256Hash, Transaction> pending;
/**
* Map of txhash->Transactions where the Transaction has unspent outputs. These are transactions we can use
* to pay other people and so count towards our balance. Transactions only appear in this map if they are part
* of the best chain. Transactions we have broacast that are not confirmed yet appear in pending even though they
* may have unspent "change" outputs.<p>
*
* Note: for now we will not allow spends of transactions that did not make it into the block chain. The code
* that handles this in BitCoin C++ is complicated. Satoshis code will not allow you to spend unconfirmed coins,
* however, it does seem to support dependency resolution entirely within the context of the memory pool so
* theoretically you could spend zero-conf coins and all of them would be included together. To simplify we'll
* make people wait but it would be a good improvement to resolve this in future.
*/
public final Map<Sha256Hash, Transaction> unspent;
/**
* Map of txhash->Transactions where the Transactions outputs are all fully spent. They are kept separately so
* the time to create a spend does not grow infinitely as wallets become more used. Some of these transactions
* may not have appeared in a block yet if they were created by us to spend coins and that spend is still being
* worked on by miners.<p>
*
* Transactions only appear in this map if they are part of the best chain.
*/
public final Map<Sha256Hash, Transaction> spent;
/**
* An inactive transaction is one that is seen only in a block that is not a part of the best chain. We keep it
* around in case a re-org promotes a different chain to be the best. In this case some (not necessarily all)
* inactive transactions will be moved out to unspent and spent, and some might be moved in.<p>
*
* Note that in the case where a transaction appears in both the best chain and a side chain as well, it is not
* placed in this map. It's an error for a transaction to be in both the inactive pool and unspent/spent.
*/
private Map<Sha256Hash, Transaction> inactive;
/**
* A dead transaction is one that's been overridden by a double spend. Such a transaction is pending except it
* will never confirm and so should be presented to the user in some unique way - flashing red for example. This
* should nearly never happen in normal usage. Dead transactions can be "resurrected" by re-orgs just like any
* other. Dead transactions are not in the pending pool.
*/
private Map<Sha256Hash, Transaction> dead;
/** A list of public/private EC keys owned by this user. */
public final ArrayList<ECKey> keychain;
private final NetworkParameters params;
transient private ArrayList<WalletEventListener> eventListeners;
/**
* Creates a new, empty wallet with no keys and no transactions. If you want to restore a wallet from disk instead,
* see loadFromFile.
*/
public Wallet(NetworkParameters params) {
this.params = params;
keychain = new ArrayList<ECKey>();
unspent = new HashMap<Sha256Hash, Transaction>();
spent = new HashMap<Sha256Hash, Transaction>();
inactive = new HashMap<Sha256Hash, Transaction>();
pending = new HashMap<Sha256Hash, Transaction>();
dead = new HashMap<Sha256Hash, Transaction>();
eventListeners = new ArrayList<WalletEventListener>();
}
/**
* Uses Java serialization to save the wallet to the given file.
*/
public synchronized void saveToFile(File f) throws IOException {
saveToFileStream(new FileOutputStream(f));
}
/**
* Uses Java serialization to save the wallet to the given file stream.
*/
public synchronized void saveToFileStream(FileOutputStream f) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(f);
oos.writeObject(this);
oos.close();
}
/**
* Returns a wallet deserialized from the given file.
*/
public static Wallet loadFromFile(File f) throws IOException {
return loadFromFileStream(new FileInputStream(f));
}
/**
* Returns a wallet deserialied from the given file input stream.
*/
public static Wallet loadFromFileStream(FileInputStream f) throws IOException {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(f);
return (Wallet) ois.readObject();
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} finally {
if (ois != null) ois.close();
}
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
eventListeners = new ArrayList<WalletEventListener>();
}
/**
* Called by the {@link BlockChain} when we receive a new block that sends coins to one of our addresses or
* spends coins from one of our addresses (note that a single transaction can do both).<p>
*
* This is necessary for the internal book-keeping Wallet does. When a transaction is received that sends us
* coins it is added to a pool so we can use it later to create spends. When a transaction is received that
* consumes outputs they are marked as spent so they won't be used in future.<p>
*
* A transaction that spends our own coins can be received either because a spend we created was accepted by the
* network and thus made it into a block, or because our keys are being shared between multiple instances and
* some other node spent the coins instead. We still have to know about that to avoid accidentally trying to
* double spend.<p>
*
* A transaction may be received multiple times if is included into blocks in parallel chains. The blockType
* parameter describes whether the containing block is on the main/best chain or whether it's on a presently
* inactive side chain. We must still record these transactions and the blocks they appear in because a future
* block might change which chain is best causing a reorganize. A re-org can totally change our balance!
*/
synchronized void receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType) throws VerificationException, ScriptException {
receive(tx, block, blockType, false);
}
private synchronized void receive(Transaction tx, StoredBlock block,
BlockChain.NewBlockType blockType, boolean reorg) throws VerificationException, ScriptException {
// Runs in a peer thread.
BigInteger prevBalance = getBalance();
Sha256Hash txHash = tx.getHash();
boolean bestChain = blockType == BlockChain.NewBlockType.BEST_CHAIN;
boolean sideChain = blockType == BlockChain.NewBlockType.SIDE_CHAIN;
BigInteger valueSentFromMe = tx.getValueSentFromMe(this);
BigInteger valueSentToMe = tx.getValueSentToMe(this);
BigInteger valueDifference = valueSentToMe.subtract(valueSentFromMe);
tx.updatedAt = new Date();
if (!reorg) {
log.info("Received tx{} for {} BTC: {}", new Object[] { sideChain ? " on a side chain" : "",
bitcoinValueToFriendlyString(valueDifference), tx.getHashAsString()});
}
// If this transaction is already in the wallet we may need to move it into a different pool. At the very
// least we need to ensure we're manipulating the canonical object rather than a duplicate.
Transaction wtx = null;
if ((wtx = pending.remove(txHash)) != null) {
log.info(" <-pending");
// A transaction we created appeared in a block. Probably this is a spend we broadcast that has been
// accepted by the network.
//
// Mark the tx as appearing in this block so we can find it later after a re-org.
wtx.addBlockAppearance(block);
if (bestChain) {
if (valueSentToMe.equals(BigInteger.ZERO)) {
// There were no change transactions so this tx is fully spent.
log.info(" ->spent");
boolean alreadyPresent = spent.put(wtx.getHash(), wtx) != null;
assert !alreadyPresent : "TX in both pending and spent pools";
} else {
// There was change back to us, or this tx was purely a spend back to ourselves (perhaps for
// anonymization purposes).
log.info(" ->unspent");
boolean alreadyPresent = unspent.put(wtx.getHash(), wtx) != null;
assert !alreadyPresent : "TX in both pending and unspent pools";
}
} else if (sideChain) {
// The transaction was accepted on an inactive side chain, but not yet by the best chain.
log.info(" ->inactive");
// It's OK for this to already be in the inactive pool because there can be multiple independent side
// chains in which it appears:
//
// b1 --> b2
// \-> b3
// \-> b4 (at this point it's already present in 'inactive'
boolean alreadyPresent = inactive.put(wtx.getHash(), wtx) != null;
if (alreadyPresent)
log.info("Saw a transaction be incorporated into multiple independent side chains");
// Put it back into the pending pool, because 'pending' means 'waiting to be included in best chain'.
pending.put(wtx.getHash(), wtx);
}
} else {
if (!reorg) {
// Mark the tx as appearing in this block so we can find it later after a re-org.
tx.addBlockAppearance(block);
}
// This TX didn't originate with us. It could be sending us coins and also spending our own coins if keys
// are being shared between different wallets.
if (sideChain) {
log.info(" ->inactive");
inactive.put(tx.getHash(), tx);
} else if (bestChain) {
processTxFromBestChain(tx);
}
}
log.info("Balance is now: " + bitcoinValueToFriendlyString(getBalance()));
// Inform anyone interested that we have new coins. Note: we may be re-entered by the event listener,
// so we must not make assumptions about our state after this loop returns! For example,
// the balance we just received might already be spent!
if (!reorg && bestChain) {
for (WalletEventListener l : eventListeners) {
synchronized (l) {
if (valueDifference.compareTo(BigInteger.ZERO) > 0) {
l.onCoinsReceived(this, tx, prevBalance, getBalance());
} else {
l.onCoinsSent(this, tx, prevBalance, getBalance());
}
}
}
}
}
public synchronized void receivePendingTransaction(Transaction tx) {
tx.updatedAt = new Date();
pending.put(tx.getHash(), tx);
for (WalletEventListener l : eventListeners) {
synchronized (l) {
l.onPendingCoinsReceived(this, tx);
}
}
}
/**
* Handle when a transaction becomes newly active on the best chain, either due to receiving a new block or a
* re-org making inactive transactions active.
*/
private void processTxFromBestChain(Transaction tx) throws VerificationException {
// This TX may spend our existing outputs even though it was not pending. This can happen in unit
// tests and if keys are moved between wallets.
updateForSpends(tx);
if (!tx.getValueSentToMe(this).equals(BigInteger.ZERO)) {
// It's sending us coins.
log.info(" new tx ->unspent");
boolean alreadyPresent = unspent.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "TX was received twice";
} else {
// It spent some of our coins and did not send us any.
log.info(" new tx ->spent");
boolean alreadyPresent = spent.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "TX was received twice";
}
}
/**
* Updates the wallet by checking if this TX spends any of our outputs. This is not used normally because
* when we receive our own spends, we've already marked the outputs as spent previously (during tx creation) so
* there's no need to go through and do it again.
*/
private void updateForSpends(Transaction tx) throws VerificationException {
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) {
// 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).
//
// Work backwards like so:
//
// A -> spent by B [pending]
// \-> spent by C [chain]
Transaction doubleSpent = input.outpoint.fromTx; // == A
Transaction connected = doubleSpent.outputs.get((int)input.outpoint.index).getSpentBy().parentTransaction;
if (pending.containsKey(connected.getHash())) {
log.info("Saw double spend from chain override pending tx {}", connected.getHashAsString());
log.info(" <-pending ->dead");
pending.remove(connected.getHash());
dead.put(connected.getHash(), connected);
// Now forcibly change the connection.
input.connect(unspent, true);
// Inform the event listeners of the newly dead tx.
for (WalletEventListener listener : eventListeners) {
synchronized (listener) {
listener.onDeadTransaction(connected, tx);
}
}
}
} else if (result == TransactionInput.ConnectionResult.SUCCESS) {
// Otherwise we saw a transaction spend our coins, but we didn't try and spend them ourselves yet.
// 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);
}
}
}
}
}
/**
* Adds an event listener object. Methods on this object are called when something interesting happens,
* like receiving money.<p>
*
* Threading: Event listener methods are dispatched on library provided threads and the both the wallet and the
* listener objects are locked during dispatch, so your listeners do not have to be thread safe. However they
* should not block as the Peer will be unresponsive to network traffic whilst your listener is running.
*/
public synchronized void addEventListener(WalletEventListener listener) {
eventListeners.add(listener);
}
/**
* Call this when we have successfully transmitted the send tx to the network, to update the wallet.
*/
public synchronized void confirmSend(Transaction tx) {
assert !pending.containsKey(tx.getHash()) : "confirmSend called on the same transaction twice";
log.info("confirmSend of {}", tx.getHashAsString());
// 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();
connectedOutput.markAsSpent(input);
}
// Some of the outputs probably send coins back to us, eg for change or because this transaction is just
// consolidating the wallet. Mark any output that is NOT back to us as spent. Then add this TX to the
// pending pool.
for (TransactionOutput output : tx.outputs) {
if (!output.isMine(this)) {
// This output didn't go to us, so by definition it is now spent.
output.markAsSpent(null);
}
}
pending.put(tx.getHash(), tx);
}
/**
* 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>
*
* This method is stateless in the sense that calling it twice with the same inputs will result in two
* Transaction objects which are equal. The wallet is not updated to track its pending status or to mark the
* coins as spent until confirmSend is called on the result.
*/
public synchronized Transaction createSend(Address address, BigInteger nanocoins) {
// For now let's just pick the first key in our keychain. In future we might want to do something else to
// give the user better privacy here, eg in incognito mode.
assert keychain.size() > 0 : "Can't send value without an address to use for receiving change";
ECKey first = keychain.get(0);
return createSend(address, nanocoins, first.toAddress(params));
}
/**
* Sends coins to the given address, via the given {@link Peer}. Change is returned to the first key in the wallet.
* @param to Which address to send coins to.
* @param nanocoins How many nanocoins to send. You can use Utils.toNanoCoins() to calculate this.
* @return The {@link Transaction} that was created or null if there was insufficient balance to send the coins.
* @throws IOException if there was a problem broadcasting the transaction
*/
public synchronized Transaction sendCoins(Peer peer, Address to, BigInteger nanocoins) throws IOException {
Transaction tx = createSend(to, nanocoins);
if (tx == null) // Not enough money! :-(
return null;
peer.broadcastTransaction(tx);
confirmSend(tx);
return tx;
}
/**
* Creates a transaction that sends $coins.$cents BTC to the given address.<p>
*
* IMPORTANT: This method does NOT update the wallet. If you call createSend again you may get two transactions
* that spend the same coins. You have to call confirmSend on the created transaction to prevent this,
* but that should only occur once the transaction has been accepted by the network. This implies you cannot have
* more than one outstanding sending tx at once.
*
* @param address The BitCoin address to send the money to.
* @param nanocoins How much currency to send, in nanocoins.
* @param changeAddress Which address to send the change to, in case we can't make exactly the right value from
* our coins. This should be an address we own (is in the keychain).
* @return a new {@link Transaction} or null if we cannot afford this send.
*/
synchronized Transaction createSend(Address address, BigInteger nanocoins, Address changeAddress) {
log.info("Creating send tx to " + address.toString() + " for " +
bitcoinValueToFriendlyString(nanocoins));
// To send money to somebody else, we need to do gather up transactions with unspent outputs until we have
// sufficient value. Many coin selection algorithms are possible, we use a simple but suboptimal one.
// TODO: Sort coins so we use the smallest first, to combat wallet fragmentation and reduce fees.
BigInteger valueGathered = BigInteger.ZERO;
List<TransactionOutput> gathered = new LinkedList<TransactionOutput>();
for (Transaction tx : unspent.values()) {
for (TransactionOutput output : tx.outputs) {
if (!output.isAvailableForSpending()) continue;
if (!output.isMine(this)) continue;
gathered.add(output);
valueGathered = valueGathered.add(output.getValue());
}
if (valueGathered.compareTo(nanocoins) >= 0) break;
}
// Can we afford this?
if (valueGathered.compareTo(nanocoins) < 0) {
log.info("Insufficient value in wallet for send, missing " +
bitcoinValueToFriendlyString(nanocoins.subtract(valueGathered)));
// TODO: Should throw an exception here.
return null;
}
assert gathered.size() > 0;
Transaction sendTx = new Transaction(params);
sendTx.addOutput(new TransactionOutput(params, sendTx, nanocoins, address));
BigInteger change = valueGathered.subtract(nanocoins);
if (change.compareTo(BigInteger.ZERO) > 0) {
// The value of the inputs is greater than what we want to send. Just like in real life then,
// we need to take back some coins ... this is called "change". Add another output that sends the change
// back to us.
log.info(" with " + bitcoinValueToFriendlyString(change) + " coins change");
sendTx.addOutput(new TransactionOutput(params, sendTx, change, changeAddress));
}
for (TransactionOutput output : gathered) {
sendTx.addInput(output);
}
// Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
try {
sendTx.signInputs(Transaction.SigHash.ALL, this);
} catch (ScriptException e) {
// If this happens it means an output script in a wallet tx could not be understood. That should never
// happen, if it does it means the wallet has got into an inconsistent state.
throw new RuntimeException(e);
}
log.info(" created {}", sendTx.getHashAsString());
return sendTx;
}
/**
* Adds the given ECKey to the wallet. There is currently no way to delete keys (that would result in coin loss).
*/
public synchronized void addKey(ECKey key) {
assert !keychain.contains(key);
keychain.add(key);
}
/**
* Locates a keypair from the keychain given the hash of the public key. This is needed when finding out which
* key we need to use to redeem a transaction output.
* @return ECKey object or null if no such key was found.
*/
public synchronized ECKey findKeyFromPubHash(byte[] pubkeyHash) {
for (ECKey key : keychain) {
if (Arrays.equals(key.getPubKeyHash(), pubkeyHash)) return key;
}
return null;
}
/** Returns true if this wallet contains a public key which hashes to the given hash. */
public synchronized boolean isPubKeyHashMine(byte[] pubkeyHash) {
return findKeyFromPubHash(pubkeyHash) != null;
}
/**
* Locates a keypair from the keychain given the raw public key bytes.
* @return ECKey or null if no such key was found.
*/
public synchronized ECKey findKeyFromPubKey(byte[] pubkey) {
for (ECKey key : keychain) {
if (Arrays.equals(key.getPubKey(), pubkey)) return key;
}
return null;
}
/** Returns true if this wallet contains a keypair with the given public key. */
public synchronized boolean isPubKeyMine(byte[] pubkey) {
return findKeyFromPubKey(pubkey) != null;
}
/**
* It's possible to calculate a wallets balance from multiple points of view. This enum selects which
* getBalance() should use.<p>
*
* Consider a real-world example: you buy a snack costing $5 but you only have a $10 bill. At the start you have
* $10 viewed from every possible angle. After you order the snack you hand over your $10 bill. From the
* perspective of your wallet you have zero dollars (AVAILABLE). But you know in a few seconds the shopkeeper
* will give you back $5 change so most people in practice would say they have $5 (ESTIMATED).<p>
*/
public enum BalanceType {
/**
* Balance calculated assuming all pending transactions are in fact included into the best chain by miners.
* This is the right balance to show in user interfaces.
*/
ESTIMATED,
/**
* Balance that can be safely used to create new spends. This is all confirmed unspent outputs minus the ones
* spent by pending transactions, but not including the outputs of those pending transactions.
*/
AVAILABLE
};
/**
* Returns the AVAILABLE balance of this wallet. See {@link BalanceType#AVAILABLE} for details on what this
* means.<p>
*
* Note: the estimated balance is usually the one you want to show to the end user - however attempting to
* actually spend these coins may result in temporary failure. This method returns how much you can safely
* provide to {@link Wallet#createSend(Address, java.math.BigInteger)}.
*/
public synchronized BigInteger getBalance() {
return getBalance(BalanceType.AVAILABLE);
}
/**
* Returns the balance of this wallet as calculated by the provided balanceType.
*/
public synchronized BigInteger getBalance(BalanceType balanceType) {
BigInteger available = BigInteger.ZERO;
for (Transaction tx : unspent.values()) {
for (TransactionOutput output : tx.outputs) {
if (!output.isMine(this)) continue;
if (!output.isAvailableForSpending()) continue;
available = available.add(output.getValue());
}
}
if (balanceType == BalanceType.AVAILABLE)
return available;
assert balanceType == BalanceType.ESTIMATED;
// Now add back all the pending outputs to assume the transaction goes through.
BigInteger estimated = available;
for (Transaction tx : pending.values()) {
for (TransactionOutput output : tx.outputs) {
if (!output.isMine(this)) continue;
estimated = estimated.add(output.getValue());
}
}
return estimated;
}
@Override
public synchronized String toString() {
StringBuilder builder = new StringBuilder();
builder.append(String.format("Wallet containing %s BTC in:\n", bitcoinValueToFriendlyString(getBalance())));
builder.append(String.format(" %d unspent transactions\n", unspent.size()));
builder.append(String.format(" %d spent transactions\n", spent.size()));
builder.append(String.format(" %d pending transactions\n", pending.size()));
builder.append(String.format(" %d inactive transactions\n", inactive.size()));
builder.append(String.format(" %d dead transactions\n", dead.size()));
// Do the keys.
builder.append("\nKeys:\n");
for (ECKey key : keychain) {
builder.append(" addr:");
builder.append(key.toAddress(params));
builder.append(" ");
builder.append(key.toString());
builder.append("\n");
}
// Print the transactions themselves
if (unspent.size() > 0) {
builder.append("\nUNSPENT:\n");
for (Transaction tx : unspent.values()) builder.append(tx);
}
if (spent.size() > 0) {
builder.append("\nSPENT:\n");
for (Transaction tx : spent.values()) builder.append(tx);
}
if (pending.size() > 0) {
builder.append("\nPENDING:\n");
for (Transaction tx : pending.values()) builder.append(tx);
}
if (inactive.size() > 0) {
builder.append("\nINACTIVE:\n");
for (Transaction tx : inactive.values()) builder.append(tx);
}
if (dead.size() > 0) {
builder.append("\nDEAD:\n");
for (Transaction tx : dead.values()) builder.append(tx);
}
return builder.toString();
}
/**
* Called by the {@link BlockChain} when the best chain (representing total work done) has changed. In this case,
* we need to go through our transactions and find out if any have become invalid. It's possible for our balance
* to go down in this case: money we thought we had can suddenly vanish if the rest of the network agrees it
* should be so.<p>
*
* The oldBlocks/newBlocks lists are ordered height-wise from top first to bottom last.
*/
synchronized void reorganize(List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks) throws VerificationException {
// This runs on any peer thread with the block chain synchronized.
//
// The reorganize functionality of the wallet is tested in ChainSplitTests.
//
// For each transaction we track which blocks they appeared in. Once a re-org takes place we have to find all
// transactions in the old branch, all transactions in the new branch and find the difference of those sets.
//
// receive() has been called on the block that is triggering the re-org before this is called.
log.info(" Old part of chain (top to bottom):");
for (StoredBlock b : oldBlocks) log.info(" {}", b.getHeader().getHashAsString());
log.info(" New part of chain (top to bottom):");
for (StoredBlock b : newBlocks) log.info(" {}", b.getHeader().getHashAsString());
// Transactions that appear in the old chain segment.
Map<Sha256Hash, Transaction> oldChainTransactions = new HashMap<Sha256Hash, Transaction>();
// Transactions that appear in the old chain segment and NOT the new chain segment.
Map<Sha256Hash, Transaction> onlyOldChainTransactions = new HashMap<Sha256Hash, Transaction>();
// Transactions that appear in the new chain segment.
Map<Sha256Hash, Transaction> newChainTransactions = new HashMap<Sha256Hash, Transaction>();
// Transactions that don't appear in either the new or the old section, ie, the shared trunk.
Map<Sha256Hash, Transaction> commonChainTransactions = new HashMap<Sha256Hash, Transaction>();
Map<Sha256Hash, Transaction> all = new HashMap<Sha256Hash, Transaction>();
all.putAll(unspent);
all.putAll(spent);
all.putAll(inactive);
for (Transaction tx : all.values()) {
Set<StoredBlock> appearsIn = tx.getAppearsIn();
assert appearsIn != null;
// If the set of blocks this transaction appears in is disjoint with one of the chain segments it means
// the transaction was never incorporated by a miner into that side of the chain.
boolean inOldSection = !Collections.disjoint(appearsIn, oldBlocks);
boolean inNewSection = !Collections.disjoint(appearsIn, newBlocks);
boolean inCommonSection = !inNewSection && !inOldSection;
if (inCommonSection) {
boolean alreadyPresent = commonChainTransactions.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "Transaction appears twice in common chain segment";
} else {
if (inOldSection) {
boolean alreadyPresent = oldChainTransactions.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "Transaction appears twice in old chain segment";
if (!inNewSection) {
alreadyPresent = onlyOldChainTransactions.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "Transaction appears twice in only-old map";
}
}
if (inNewSection) {
boolean alreadyPresent = newChainTransactions.put(tx.getHash(), tx) != null;
assert !alreadyPresent : "Transaction appears twice in new chain segment";
}
}
}
// If there is no difference it means we have nothing we need to do and the user does not care.
boolean affectedUs = !oldChainTransactions.equals(newChainTransactions);
log.info(affectedUs ? "Re-org affected our transactions" : "Re-org had no effect on our transactions");
if (!affectedUs) return;
// For simplicity we will reprocess every transaction to ensure it's in the right bucket and has the right
// connections. Attempting to update each one with minimal work is possible but complex and was leading to
// edge cases that were hard to fix. As re-orgs are rare the amount of work this implies should be manageable
// unless the user has an enormous wallet. As an optimization fully spent transactions buried deeper than
// 1000 blocks could be put into yet another bucket which we never touch and assume re-orgs cannot affect.
for (Transaction tx : onlyOldChainTransactions.values()) log.info(" Only Old: {}", tx.getHashAsString());
for (Transaction tx : oldChainTransactions.values()) log.info(" Old: {}", tx.getHashAsString());
for (Transaction tx : newChainTransactions.values()) log.info(" New: {}", tx.getHashAsString());
// Break all the existing connections.
for (Transaction tx : all.values())
tx.disconnectInputs();
for (Transaction tx : pending.values())
tx.disconnectInputs();
// Reconnect the transactions in the common part of the chain.
for (Transaction tx : commonChainTransactions.values()) {
TransactionInput badInput = tx.connectInputs(all, false);
assert badInput == null : "Failed to connect " + tx.getHashAsString() + ", " + badInput.toString();
}
// Recalculate the unspent/spent buckets for the transactions the re-org did not affect.
unspent.clear();
spent.clear();
inactive.clear();
for (Transaction tx : commonChainTransactions.values()) {
int unspentOutputs = 0;
for (TransactionOutput output : tx.outputs) {
if (output.isAvailableForSpending()) unspentOutputs++;
}
if (unspentOutputs > 0) {
log.info(" TX {}: ->unspent", tx.getHashAsString());
unspent.put(tx.getHash(), tx);
} else {
log.info(" TX {}: ->spent", tx.getHashAsString());
spent.put(tx.getHash(), tx);
}
}
// Now replay the act of receiving the blocks that were previously in a side chain. This will:
// - Move any transactions that were pending and are now accepted into the right bucket.
// - Connect the newly active transactions.
Collections.reverse(newBlocks); // Need bottom-to-top but we get top-to-bottom.
for (StoredBlock b : newBlocks) {
log.info("Replaying block {}", b.getHeader().getHashAsString());
Set<Transaction> txns = new HashSet<Transaction>();
for (Transaction tx : newChainTransactions.values()) {
if (tx.appearsIn.contains(b)) {
txns.add(tx);
log.info(" containing tx {}", tx.getHashAsString());
}
}
for (Transaction t : txns) {
try {
receive(t, b, BlockChain.NewBlockType.BEST_CHAIN, true);
} catch (ScriptException e) {
throw new RuntimeException(e); // Cannot happen as these blocks were already verified.
}
}
}
// Find the transactions that didn't make it into the new chain yet. For each input, try to connect it to the
// transactions that are in {spent,unspent,pending}. Check the status of each input. For inactive
// transactions that only send us money, we put them into the inactive pool where they sit around waiting for
// another re-org or re-inclusion into the main chain. For inactive transactions where we spent money we must
// put them back into the pending pool if we can reconnect them, so we don't create a double spend whilst the
// network heals itself.
Map<Sha256Hash, Transaction> pool = new HashMap<Sha256Hash, Transaction>();
pool.putAll(unspent);
pool.putAll(spent);
pool.putAll(pending);
Map<Sha256Hash, Transaction> toReprocess = new HashMap<Sha256Hash, Transaction>();
toReprocess.putAll(onlyOldChainTransactions);
toReprocess.putAll(pending);
log.info("Reprocessing:");
// Note, we must reprocess dead transactions first. The reason is that if there is a double spend across
// chains from our own coins we get a complicated situation:
//
// 1) We switch to a new chain (B) that contains a double spend overriding a pending transaction. It goes dead.
// 2) We switch BACK to the first chain (A). The dead transaction must go pending again.
// 3) We resurrect the transactions that were in chain (B) and assume the miners will start work on putting them
// in to the chain, but it's not possible because it's a double spend. So now that transaction must become
// dead instead of pending.
//
// This only occurs when we are double spending our own coins.
for (Transaction tx : dead.values()) {
reprocessTxAfterReorg(pool, tx);
}
for (Transaction tx : toReprocess.values()) {
reprocessTxAfterReorg(pool, tx);
}
log.info("post-reorg balance is {}", Utils.bitcoinValueToFriendlyString(getBalance()));
// Inform event listeners that a re-org took place.
for (WalletEventListener l : eventListeners) {
// Synchronize on the event listener as well. This allows a single listener to handle events from
// multiple wallets without needing to worry about being thread safe.
synchronized (l) {
l.onReorganize();
}
}
}
private void reprocessTxAfterReorg(Map<Sha256Hash, Transaction> pool, Transaction tx) {
log.info(" TX {}", tx.getHashAsString());
int numInputs = tx.inputs.size();
int noSuchTx = 0;
int success = 0;
boolean isDead = false;
for (TransactionInput input : tx.inputs) {
if (input.isCoinBase()) {
// Input is not in our wallet so there is "no such input tx", bit of an abuse.
noSuchTx++;
continue;
}
TransactionInput.ConnectionResult result = input.connect(pool, false);
if (result == TransactionInput.ConnectionResult.SUCCESS) {
success++;
} else if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
noSuchTx++;
} else if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
isDead = true;
// This transaction was replaced by a double spend on the new chain. Did you just reverse
// your own transaction? I hope not!!
log.info(" ->dead, will not confirm now unless there's another re-org", tx.getHashAsString());
TransactionOutput doubleSpent = input.getConnectedOutput(pool);
Transaction replacement = doubleSpent.getSpentBy().parentTransaction;
dead.put(tx.getHash(), tx);
pending.remove(tx.getHash());
// Inform the event listeners of the newly dead tx.
for (WalletEventListener listener : eventListeners) {
synchronized (listener) {
listener.onDeadTransaction(tx, replacement);
}
}
break;
}
}
if (isDead) return;
if (noSuchTx == numInputs) {
log.info(" ->inactive", tx.getHashAsString());
inactive.put(tx.getHash(), tx);
} else if (success == numInputs - noSuchTx) {
// All inputs are either valid for spending or don't come from us. Miners are trying to reinclude it.
log.info(" ->pending", tx.getHashAsString());
pending.put(tx.getHash(), tx);
dead.remove(tx.getHash());
}
}
/**
* Returns an immutable view of the transactions currently waiting for network confirmations.
*/
public Collection<Transaction> getPendingTransactions() {
return Collections.unmodifiableCollection(pending.values());
}
public ArrayList<Transaction> getAllTransactions(){
// generate list of transactions to show
ArrayList<Transaction> transactions = new ArrayList<Transaction>();
transactions.addAll(pending.values());
transactions.addAll(unspent.values());
transactions.addAll(spent.values());
// make sure list is unique
transactions = new ArrayList<Transaction>(new HashSet<Transaction>(transactions));
// Sort by time
Collections.sort(transactions, new Comparator<Transaction>() {
public int compare(Transaction t1, Transaction t2) {
if (t1.updatedAt == null)
t1.updatedAt = new Date(0);
if (t2.updatedAt == null)
t2.updatedAt = new Date(0);
return t2.updatedAt.compareTo(t1.updatedAt);
}
});
return transactions;
}
}