/**
* 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 java.io.*;
import java.math.BigInteger;
import java.util.*;
import static com.google.bitcoin.core.Utils.LOG;
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. Once a transaction
* output is used, it is removed from the wallet as it is no longer available for spending.<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 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
//
// Re-orgs:
// 1. Tx is present in old chain and not present in new chain
// <-unspent/spent ->inactive
//
// 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. Until then we do NOT consider them
// pending as it's possible some of the transactions have become invalid (eg because the new chain contains
// a double spend). This could cause some confusing UI changes for the user but these events should be very
// rare.
//
// 2. Tx is not present in old chain and is present in new chain
// <-inactive and ->unspent/spent
//
// Balance:
// 1. Sum up all unspent outputs of the transactions in unspent.
// 2. Subtract the inputs of transactions in pending.
// 3. In future: re-add the outputs of pending transactions that are mine. Don't do this today because those
// change outputs would not be considered spendable.
/**
* Map of txhash->Transactions that have not made it into the best chain yet. 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!
*/
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.
*/
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.
*/
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 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>();
eventListeners = new ArrayList<WalletEventListener>();
}
/**
* Uses Java serialization to save the wallet to the given file.
*/
public synchronized void saveToFile(File f) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(f));
oos.writeObject(this);
oos.close();
}
/**
* Returns a wallet deserialized from the given file.
*/
public static Wallet loadFromFile(File f) throws IOException {
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(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>();
}
/**
* Returns true if the given transaction is present in the wallet, comparing by hash value (not by object
* reference). So you can create a transaction object from scratch and get true from this method if the
* transaction is logically equal.
*/
public synchronized boolean isTransactionPresent(Transaction transaction) {
// TODO: Redefine or delete this method.
Sha256Hash hash = transaction.getHash();
return unspent.containsKey(hash) || spent.containsKey(hash);
}
/**
* 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 {
// 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);
LOG("Wallet: Received tx" + (sideChain ? " on a side chain" :"") + " for " +
bitcoinValueToFriendlyString(valueDifference) + " BTC");
// 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(" <-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(" ->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(" ->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(" ->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("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 {
// 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(" ->inactive");
inactive.put(tx.getHash(), tx);
} else if (bestChain) {
processTxFromBestChain(tx);
}
}
LOG("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 (bestChain && valueDifference.compareTo(BigInteger.ZERO) > 0) {
for (WalletEventListener l : eventListeners) {
synchronized (l) {
l.onCoinsReceived(this, tx, prevBalance, getBalance());
}
}
}
}
/**
* 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(" ->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(" ->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 unspent 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) {
if (input.outpoint.connect(unspent.values())) {
TransactionOutput output = input.outpoint.getConnectedOutput();
assert !output.isSpent : "Double spend accepted by the network?";
LOG(" Saw some of my unspent outputs be spent by someone else who has my keys.");
LOG(" Total spent value is " + bitcoinValueToFriendlyString(output.getValue()));
output.isSpent = true;
Transaction connectedTx = input.outpoint.fromTx;
if (connectedTx.getValueSentToMe(this, false).equals(BigInteger.ZERO)) {
// There's nothing left I can spend in this transaction.
if (unspent.remove(connectedTx.getHash()) != null);
LOG(" prevtx <-unspent");
spent.put(connectedTx.getHash(), connectedTx);
LOG(" prevtx ->spent");
}
}
}
}
/**
* 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.
*/
synchronized void confirmSend(Transaction tx) {
assert !pending.containsKey(tx) : "confirmSend called on the same transaction twice";
// Mark each connected output of the tx as spent, so we don't try and spend it again.
for (TransactionInput input : tx.inputs) {
TransactionOutput connectedOutput = input.outpoint.getConnectedOutput();
assert !connectedOutput.isSpent : "createSend called before corresponding confirmSend";
connectedOutput.isSpent = true;
}
// 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.
assert !output.isSpent;
output.isSpent = true;
}
}
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.
*/
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("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.isSpent) 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("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, nanocoins, address, sendTx));
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(" with " + bitcoinValueToFriendlyString(change) + " coins change");
sendTx.addOutput(new TransactionOutput(params, change, changeAddress, sendTx));
}
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);
}
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;
}
/**
* Returns the balance of this wallet by summing up all unspent outputs that were sent to us.
*/
public synchronized BigInteger getBalance() {
BigInteger balance = BigInteger.ZERO;
for (Transaction tx : unspent.values()) {
for (TransactionOutput output : tx.outputs) {
if (output.isSpent) continue;
if (!output.isMine(this)) continue;
balance = balance.add(output.getValue());
}
}
return balance;
}
@Override
public synchronized String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Wallet containing ");
builder.append(bitcoinValueToFriendlyString(getBalance()));
builder.append("BTC in ");
builder.append(unspent.size());
builder.append(" unspent transactions/");
builder.append(spent.size());
builder.append(" spent transactions");
// 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");
}
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.
*/
synchronized void reorganize(Set<StoredBlock> oldBlocks, Set<StoredBlock> newBlocks) throws VerificationException {
// This runs on any peer thread with the block chain synchronized.
//
// The reorganize functionality of the wallet is tested in the BlockChainTest.testForking* methods.
//
// 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.
Set<Transaction> oldChainTransactions = new HashSet<Transaction>();
Set<Transaction> newChainTransactions = new HashSet<Transaction>();
Set<Transaction> all = new HashSet<Transaction>();
all.addAll(unspent.values());
all.addAll(spent.values());
all.addAll(inactive.values());
for (Transaction tx : all) {
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.
if (!Collections.disjoint(appearsIn, oldBlocks)) {
boolean alreadyPresent = !oldChainTransactions.add(tx);
assert !alreadyPresent : "Transaction appears twice in chain segment";
}
if (!Collections.disjoint(appearsIn, newBlocks)) {
boolean alreadyPresent = !newChainTransactions.add(tx);
assert !alreadyPresent : "Transaction appears twice in chain segment";
}
}
// If there is no difference it means we the user doesn't really care about this re-org but we still need to
// update the transaction block pointers for next time.
boolean affectedUs = !oldChainTransactions.equals(newChainTransactions);
LOG(affectedUs ? "Re-org affected our transactions" : "Re-org had no effect on our transactions");
if (!affectedUs) return;
// Transactions that were in the old chain but aren't in the new chain. These will become inactive.
Set<Transaction> gone = new HashSet<Transaction>(oldChainTransactions);
gone.removeAll(newChainTransactions);
// Transactions that are in the new chain but aren't in the old chain. These will be re-processed.
Set<Transaction> fresh = new HashSet<Transaction>(newChainTransactions);
fresh.removeAll(oldChainTransactions);
assert !(gone.isEmpty() && fresh.isEmpty()) : "There must have been some changes to get here";
for (Transaction tx : gone) {
LOG("tx not in new chain: <-unspent/spent ->inactive\n" + tx.toString());
unspent.remove(tx.getHash());
spent.remove(tx.getHash());
inactive.put(tx.getHash(), tx);
// We do not put it into the pending pool. Pending is for transactions we know are valid. After a re-org
// some transactions may become permanently invalid if the new chain contains a double spend. We don't
// want transactions sitting in the pending pool forever. This means shortly after a re-org the balance
// might change rapidly as newly transactions are resurrected and included into the new chain by miners.
}
for (Transaction tx : fresh) {
inactive.remove(tx.getHash());
processTxFromBestChain(tx);
}
// 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();
}
}
}
/**
* Returns an immutable view of the transactions currently waiting for network confirmations.
*/
public Collection<Transaction> getPendingTransactions() {
return Collections.unmodifiableCollection(pending.values());
}
}