com.google.bitcoin.core.Wallet.java Source code

Java tutorial

Introduction

Here is the source code for com.google.bitcoin.core.Wallet.java

Source

/**
 * Copyright 2013 Google Inc.
 * Copyright 2014 Andreas Schildbach
 *
 * 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 com.google.bitcoin.IsMultiBitClass;
import com.google.bitcoin.core.TransactionConfidence.ConfidenceType;
import com.google.bitcoin.crypto.KeyCrypter;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.crypto.KeyCrypterScrypt;
import com.google.bitcoin.script.Script;
import com.google.bitcoin.script.ScriptBuilder;
import com.google.bitcoin.script.ScriptChunk;
import com.google.bitcoin.store.UnreadableWalletException;
import com.google.bitcoin.store.WalletProtobufSerializer;
import com.google.bitcoin.utils.ListenerRegistration;
import com.google.bitcoin.utils.Threading;
import com.google.bitcoin.wallet.*;
import com.google.bitcoin.wallet.WalletTransaction.Pool;
import com.google.common.collect.*;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.multibit.store.MultiBitWalletExtension;
import org.multibit.store.MultiBitWalletProtobufSerializer;
import org.multibit.store.MultiBitWalletVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.util.encoders.Hex;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import java.io.*;
import java.math.BigInteger;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

import static com.google.bitcoin.core.Utils.bitcoinValueToFriendlyString;
import static com.google.bitcoin.core.Utils.bitcoinValueToPlainString;
import static com.google.common.base.Preconditions.*;
//import com.sun.org.apache.xml.internal.security.utils.Base64;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.coinspark.core.CSExceptions;
import org.coinspark.core.CSLogger;
import org.coinspark.core.CSUtils;
import org.coinspark.protocol.CoinSparkBase;
import org.coinspark.protocol.CoinSparkIORange;
import org.coinspark.protocol.CoinSparkMessage;
import org.coinspark.protocol.CoinSparkMessagePart;
import org.coinspark.protocol.CoinSparkTransfer;
import org.coinspark.protocol.CoinSparkTransferList;
import org.coinspark.wallet.CSAsset;
import org.coinspark.wallet.CSAssetDatabase;
import org.coinspark.wallet.CSBalance;
import org.coinspark.wallet.CSBalanceDatabase;
import org.coinspark.wallet.CSMessage;
import org.coinspark.wallet.CSMessageDatabase;
import org.coinspark.wallet.CSTransactionAssets;
import org.coinspark.wallet.CSTransactionOutput;
import org.coinspark.protocol.CoinSparkPaymentRef;
import org.coinspark.wallet.CSEventType;
import org.coinspark.wallet.CSEventBus;
import org.coinspark.wallet.CSNonce;
import org.coinspark.wallet.CSMessagePart;

import org.h2.mvstore.MVMap;
import org.apache.commons.codec.binary.Base64;

// To do list:
//
// This whole class has evolved over a period of years and needs a ground-up rewrite.
//
// - Take all wallet-relevant data out of Transaction and put it into WalletTransaction. Make Transaction immutable.
// - Only store relevant transaction outputs, don't bother storing the rest of the data.
// - Split block chain and tx output tracking into a superclass that doesn't have any key or spending related code.
// - Simplify how transactions are tracked and stored: in particular, have the wallet maintain positioning information
//   for transactions independent of the transactions themselves, so the timeline can be walked without having to
//   process and sort every single transaction.
// - Decompose the class where possible: break logic out into classes that can be customized/replaced by the user.
//     - [Auto]saving to a backing store
//     - Key management
//     - just generally make Wallet smaller and easier to work with
// - Make clearing of transactions able to only rewind the wallet a certain distance instead of all blocks.
// - Make it scale:
//     - eliminate all the algorithms with quadratic complexity (or worse)
//     - don't require everything to be held in RAM at once
//     - consider allowing eviction of no longer re-orgable transactions or keys that were used up

/**
 * <p>A Wallet stores keys and a record of transactions that send and receive value from those keys. Using these,
 * it is able to create new transactions that spend the recorded transactions, and this is the fundamental operation
 * of the Bitcoin protocol.</p>
 *
 * <p>To learn more about this class, read <b><a href="http://code.google.com/p/bitcoinj/wiki/WorkingWithTheWallet">
 *     working with the wallet.</a></b></p>
 *
 * <p>To fill up a Wallet with transactions, you need to use it in combination with a {@link BlockChain} and various
 * other objects, see the <a href="http://code.google.com/p/bitcoinj/wiki/GettingStarted">Getting started</a> tutorial
 * on the website to learn more about how to set everything up.</p>
 *
 * <p>Wallets can be serialized using either Java serialization - this is not compatible across versions of bitcoinj,
 * or protocol buffer serialization. You need to save the wallet whenever it changes, there is an auto-save feature
 * that simplifies this for you although you're still responsible for manually triggering a save when your app is about
 * to quit because the auto-save feature waits a moment before actually committing to disk to avoid IO thrashing when
 * the wallet is changing very fast (eg due to a block chain sync). See
 * {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.wallet.WalletFiles.Listener)}
 * for more information about this.</p>
 */
public class Wallet implements Serializable, BlockChainListener, PeerFilterProvider, IsMultiBitClass {
    private static final Logger log = LoggerFactory.getLogger(Wallet.class);
    private static final long serialVersionUID = 2L;
    private static final int MINIMUM_BLOOM_DATA_LENGTH = 8;

    protected final ReentrantLock lock = Threading.lock("wallet");

    // The various pools below give quick access to wallet-relevant transactions by the state they're in:
    //
    // Pending:  Transactions that didn't make it into the best chain yet. Pending transactions can be killed if a
    //           double-spend against them appears in the best chain, in which case they move to the dead pool.
    //           If a double-spend appears in the pending state as well, currently we just ignore the second
    //           and wait for the miners to resolve the race.
    // Unspent:  Transactions that appeared in the best chain and have outputs we can spend. Note that we store the
    //           entire transaction in memory even though for spending purposes we only really need the outputs, the
    //           reason being that this simplifies handling of re-orgs. It would be worth fixing this in future.
    // Spent:    Transactions that appeared in the best chain but don't have any spendable outputs. They're stored here
    //           for history browsing/auditing reasons only and in future will probably be flushed out to some other
    //           kind of cold storage or just removed.
    // Dead:     Transactions that we believe will never confirm get moved here, out of pending. Note that the Satoshi
    //           client has no notion of dead-ness: the assumption is that double spends won't happen so there's no
    //           need to notify the user about them. We take a more pessimistic approach and try to track the fact that
    //           transactions have been double spent so applications can do something intelligent (cancel orders, show
    //           to the user in the UI, etc). A transaction can leave dead and move into spent/unspent if there is a
    //           re-org to a chain that doesn't include the double spend.

    final Map<Sha256Hash, Transaction> pending;
    final Map<Sha256Hash, Transaction> unspent;
    final Map<Sha256Hash, Transaction> spent;
    final Map<Sha256Hash, Transaction> dead;

    // All transactions together.
    final Map<Sha256Hash, Transaction> transactions;

    // A list of public/private EC keys owned by this user. Access it using addKey[s], hasKey[s] and findPubKeyFromHash.
    private ArrayList<ECKey> keychain;

    // A list of scripts watched by this wallet.
    private Set<Script> watchedScripts;

    private NetworkParameters params;

    @Nullable
    private Sha256Hash lastBlockSeenHash;
    private int lastBlockSeenHeight;
    private long lastBlockSeenTimeSecs;

    private transient CopyOnWriteArrayList<ListenerRegistration<WalletEventListener>> eventListeners;

    // A listener that relays confidence changes from the transaction confidence object to the wallet event listener,
    // as a convenience to API users so they don't have to register on every transaction themselves.
    private transient TransactionConfidence.Listener txConfidenceListener;

    // If a TX hash appears in this set then notifyNewBestBlock will ignore it, as its confidence was already set up
    // in receive() via Transaction.setBlockAppearance(). As the BlockChain always calls notifyNewBestBlock even if
    // it sent transactions to the wallet, without this we'd double count.
    private transient HashSet<Sha256Hash> ignoreNextNewBlock;
    // Whether or not to ignore nLockTime > 0 transactions that are received to the mempool.
    private boolean acceptRiskyTransactions;

    // Stuff for notifying transaction objects that we changed their confidences. The purpose of this is to avoid
    // spuriously sending lots of repeated notifications to listeners that API users aren't really interested in as a
    // side effect of how the code is written (e.g. during re-orgs confidence data gets adjusted multiple times).
    private int onWalletChangedSuppressions;
    private boolean insideReorg;
    private Map<Transaction, TransactionConfidence.Listener.ChangeReason> confidenceChanged;
    private volatile WalletFiles vFileManager;
    // Object that is used to send transactions asynchronously when the wallet requires it.
    private volatile TransactionBroadcaster vTransactionBroadcaster;
    // UNIX time in seconds. Money controlled by keys created before this time will be automatically respent to a key
    // that was created after it. Useful when you believe some keys have been compromised.
    private volatile long vKeyRotationTimestamp;
    private volatile boolean vKeyRotationEnabled;

    private transient CoinSelector coinSelector = new DefaultCoinSelector();

    // The keyCrypter for the wallet. This specifies the algorithm used for encrypting and decrypting the private keys.
    private KeyCrypter keyCrypter;

    /**
     * The wallet version. This can be used to track breaking changes in the wallet format.
     * You can also use it to detect wallets that come from the future (ie they contain features you
     * do not know how to deal with).
     */
    MultiBitWalletVersion version;

    /**
     * A description for the wallet.
     */
    String description;

    // Stores objects that know how to serialize/unserialize themselves to byte streams and whether they're mandatory
    // or not. The string key comes from the extension itself.
    private final HashMap<String, WalletExtension> extensions;
    // Object that performs risk analysis of received pending transactions. We might reject transactions that seem like
    // a high risk of being a double spending attack.
    private RiskAnalysis.Analyzer riskAnalyzer = DefaultRiskAnalysis.FACTORY;

    /**
     * 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 = checkNotNull(params);
        keychain = new ArrayList<ECKey>();
        watchedScripts = Sets.newHashSet();
        unspent = new HashMap<Sha256Hash, Transaction>();
        spent = new HashMap<Sha256Hash, Transaction>();
        pending = new HashMap<Sha256Hash, Transaction>();
        dead = new HashMap<Sha256Hash, Transaction>();
        transactions = new HashMap<Sha256Hash, Transaction>();
        eventListeners = new CopyOnWriteArrayList<ListenerRegistration<WalletEventListener>>();
        extensions = new HashMap<String, WalletExtension>();

        /* CSPK-mike START */
        // CoinSpark object initialization.
        CS = new CoinSpark(this);
        /* CSPK-mike END */

        if (keyCrypter != null) {
            // If the wallet is encrypted, add a wallet protect extension.
            MultiBitWalletExtension multibitWalletExtension = new MultiBitWalletExtension();
            extensions.put(multibitWalletExtension.getWalletExtensionID(), multibitWalletExtension);

            // The wallet version indicates the wallet is encrypted.
            setVersion(MultiBitWalletVersion.PROTOBUF_ENCRYPTED);
        } else {
            // The wallet version indicates the wallet is unencrypted.
            setVersion(MultiBitWalletVersion.PROTOBUF);
        }
        confidenceChanged = new HashMap<Transaction, TransactionConfidence.Listener.ChangeReason>();
        createTransientState();
    }

    /**
     * Create a wallet with a keyCrypter to use in encrypting and decrypting keys.
     */
    public Wallet(NetworkParameters params, KeyCrypter keyCrypter) {
        this(params);
        this.keyCrypter = checkNotNull(keyCrypter);

        // The wallet is encrypted, add a wallet protect extension.
        MultiBitWalletExtension multibitWalletExtension = new MultiBitWalletExtension();
        extensions.put(multibitWalletExtension.getWalletExtensionID(), multibitWalletExtension);

        // The wallet version indicates the wallet is encrypted.
        setVersion(MultiBitWalletVersion.PROTOBUF_ENCRYPTED);
    }

    private void createTransientState() {
        ignoreNextNewBlock = new HashSet<Sha256Hash>();
        txConfidenceListener = new TransactionConfidence.Listener() {
            @Override
            public void onConfidenceChanged(Transaction tx, TransactionConfidence.Listener.ChangeReason reason) {
                // This will run on the user code thread so we shouldn't do anything too complicated here.
                // We only want to queue a wallet changed event and auto-save if the number of peers announcing
                // the transaction has changed, as that confidence change is made by the networking code which
                // doesn't necessarily know at that point which wallets contain which transactions, so it's up
                // to us to listen for that. Other types of confidence changes (type, etc) are triggered by us,
                // so we'll queue up a wallet change event in other parts of the code.
                if (reason == ChangeReason.SEEN_PEERS) {
                    lock.lock();
                    try {
                        checkBalanceFuturesLocked(null);
                        queueOnTransactionConfidenceChanged(tx);
                        maybeQueueOnWalletChanged();
                    } finally {
                        lock.unlock();
                    }
                }
            }
        };
        coinSelector = new DefaultCoinSelector();
        acceptRiskyTransactions = false;
    }

    public NetworkParameters getNetworkParameters() {
        return params;
    }

    public void setNetworkParameters(NetworkParameters params) {
        this.params = params;
    }

    /** Returns the parameters this wallet was created with. */
    public NetworkParameters getParams() {
        return params;
    }

    /**
     * Returns a snapshot of the keychain. This view is not live.
     */
    public List<ECKey> getKeys() {
        lock.lock();
        try {
            return new ArrayList<ECKey>(keychain);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a snapshot of the watched scripts. This view is not live.
     */
    public List<Script> getWatchedScripts() {
        lock.lock();
        try {
            return new ArrayList<Script>(watchedScripts);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Removes the given key from the keychain. Be very careful with this - losing a private key <b>destroys the
     * money associated with it</b>.
     * @return Whether the key was removed or not.
     */
    public boolean removeKey(ECKey key) {
        lock.lock();
        try {
            return keychain.remove(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a snapshot of the keychain. This view is live.
     */
    public List<ECKey> getKeychain() {
        lock.lock();
        try {
            return keychain;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the number of keys in the keychain.
     */
    public int getKeychainSize() {
        lock.lock();
        try {
            return keychain.size();
        } finally {
            lock.unlock();
        }
    }

    /** Saves the wallet first to the given temp file, then renames to the dest file. */
    public void saveToFile(File temp, File destFile) throws IOException {
        FileOutputStream stream = null;
        lock.lock();
        try {
            stream = new FileOutputStream(temp);
            saveToFileStream(stream);
            // Attempt to force the bits to hit the disk. In reality the OS or hard disk itself may still decide
            // to not write through to physical media for at least a few seconds, but this is the best we can do.
            stream.flush();
            stream.getFD().sync();
            stream.close();
            stream = null;
            if (Utils.isWindows()) {
                // Work around an issue on Windows whereby you can't rename over existing files.
                File canonical = destFile.getCanonicalFile();
                canonical.delete();
                if (temp.renameTo(canonical))
                    return; // else fall through.
                throw new IOException("Failed to rename " + temp + " to " + canonical);
            } else if (!temp.renameTo(destFile)) {
                throw new IOException("Failed to rename " + temp + " to " + destFile);
            }
        } catch (RuntimeException e) {
            log.error("Failed whilst saving wallet", e);
            throw e;
        } finally {
            lock.unlock();
            if (stream != null) {
                stream.close();
            }
        }
    }

    /**
     * Uses protobuf serialization to save the wallet to the given file. To learn more about this file format, see
     * {@link WalletProtobufSerializer}. Writes out first to a temporary file in the same directory and then renames
     * once written.
     */
    public void saveToFile(File f) throws IOException {
        File directory = f.getAbsoluteFile().getParentFile();
        File temp = File.createTempFile("wallet", null, directory);
        saveToFile(temp, f);
    }

    /**
     * <p>Whether or not the wallet will ignore received pending transactions that fail the selected
     * {@link RiskAnalysis}. By default, if a transaction is considered risky then it won't enter the wallet
     * and won't trigger any event listeners. If you set this property to true, then all transactions will
     * be allowed in regardless of risk. Currently, the {@link DefaultRiskAnalysis} checks for non-finality of
     * transactions. You should not encounter these outside of special protocols.</p>
     *
     * <p>Note that this property is not serialized. You have to set it each time a Wallet object is constructed,
     * even if it's loaded from a protocol buffer.</p>
     */
    public void setAcceptRiskyTransactions(boolean acceptRiskyTransactions) {
        lock.lock();
        try {
            this.acceptRiskyTransactions = acceptRiskyTransactions;
        } finally {
            lock.unlock();
        }
    }

    /**
     * See {@link Wallet#setAcceptRiskyTransactions(boolean)} for an explanation of this property.
     */
    public boolean doesAcceptRiskyTransactions() {
        lock.lock();
        try {
            return acceptRiskyTransactions;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Sets the {@link RiskAnalysis} implementation to use for deciding whether received pending transactions are risky
     * or not. If the analyzer says a transaction is risky, by default it will be dropped. You can customize this
     * behaviour with {@link #setAcceptRiskyTransactions(boolean)}.
     */
    public void setRiskAnalyzer(RiskAnalysis.Analyzer analyzer) {
        lock.lock();
        try {
            this.riskAnalyzer = checkNotNull(analyzer);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Gets the current {@link RiskAnalysis} implementation. The default is {@link DefaultRiskAnalysis}.
     */
    public RiskAnalysis.Analyzer getRiskAnalyzer() {
        lock.lock();
        try {
            return riskAnalyzer;
        } finally {
            lock.unlock();
        }
    }

    /**
     * <p>Sets up the wallet to auto-save itself to the given file, using temp files with atomic renames to ensure
     * consistency. After connecting to a file, you no longer need to save the wallet manually, it will do it
     * whenever necessary. Protocol buffer serialization will be used.</p>
     *
     * <p>If delayTime is set, a background thread will be created and the wallet will only be saved to
     * disk every so many time units. If no changes have occurred for the given time period, nothing will be written.
     * In this way disk IO can be rate limited. It's a good idea to set this as otherwise the wallet can change very
     * frequently, eg if there are a lot of transactions in it or during block sync, and there will be a lot of redundant
     * writes. Note that when a new key is added, that always results in an immediate save regardless of
     * delayTime. <b>You should still save the wallet manually when your program is about to shut down as the JVM
     * will not wait for the background thread.</b></p>
     *
     * <p>An event listener can be provided. If a delay >0 was specified, it will be called on a background thread
     * with the wallet locked when an auto-save occurs. If delay is zero or you do something that always triggers
     * an immediate save, like adding a key, the event listener will be invoked on the calling threads.</p>
     *
     * @param f The destination file to save to.
     * @param delayTime How many time units to wait until saving the wallet on a background thread.
     * @param timeUnit the unit of measurement for delayTime.
     * @param eventListener callback to be informed when the auto-save thread does things, or null
     */
    public WalletFiles autosaveToFile(File f, long delayTime, TimeUnit timeUnit,
            @Nullable WalletFiles.Listener eventListener) {
        lock.lock();
        try {
            checkState(vFileManager == null, "Already auto saving this wallet.");
            WalletFiles manager = new WalletFiles(this, f, delayTime, timeUnit);
            if (eventListener != null)
                manager.setListener(eventListener);
            vFileManager = manager;
            return manager;
        } finally {
            lock.unlock();
        }
    }

    /**
     * <p>
     * Disables auto-saving, after it had been enabled with
     * {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.wallet.WalletFiles.Listener)}
     * before. This method blocks until finished.
     * </p>
     */
    public void shutdownAutosaveAndWait() {
        lock.lock();
        try {
            WalletFiles files = vFileManager;
            vFileManager = null;
            checkState(files != null, "Auto saving not enabled.");
            files.shutdownAndWait();
        } finally {
            lock.unlock();
        }
    }

    private void saveLater() {
        WalletFiles files = vFileManager;
        if (files != null)
            files.saveLater();
    }

    /** If auto saving is enabled, do an immediate sync write to disk ignoring any delays. */
    private void saveNow() {
        WalletFiles files = vFileManager;
        if (files != null) {
            try {
                files.saveNow(); // This calls back into saveToFile().
            } catch (IOException e) {
                // Can't really do much at this point, just let the API user know.
                log.error("Failed to save wallet to disk!", e);
                Thread.UncaughtExceptionHandler handler = Threading.uncaughtExceptionHandler;
                if (handler != null)
                    handler.uncaughtException(Thread.currentThread(), e);
            }
        }
    }

    /**
     * Uses protobuf serialization to save the wallet to the given file stream. To learn more about this file format, see
     * {@link WalletProtobufSerializer}.
     */
    public void saveToFileStream(OutputStream f) throws IOException {
        lock.lock();
        try {
            new MultiBitWalletProtobufSerializer().writeWallet(this, f);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a wallet deserialized from the given file.
     */
    public static Wallet loadFromFile(File f) throws UnreadableWalletException {
        try {
            FileInputStream stream = null;
            try {
                stream = new FileInputStream(f);
                return loadFromFileStream(stream);
            } finally {
                if (stream != null)
                    stream.close();
            }
        } catch (IOException e) {
            throw new UnreadableWalletException("Could not open file", e);
        }
    }

    public boolean isConsistent() {
        // Seems too aggressive on replay, switch off.
        return true;
    }

    /**
     * Returns a wallet deserialized from the given input stream.
     */
    public static Wallet loadFromFileStream(InputStream stream) throws UnreadableWalletException {
        Wallet wallet;

        wallet = new MultiBitWalletProtobufSerializer().readWallet(stream);
        if (!wallet.isConsistent()) {
            log.error("Loaded an inconsistent wallet");
        }

        return wallet;
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        createTransientState();
    }

    /**
     * Called by the {@link BlockChain} when we receive a new filtered block that contains a transactions previously
     * received by a call to @{link receivePending}.<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!
     */
    public void notifyTransactionIsInBlock(Sha256Hash txHash, StoredBlock block, BlockChain.NewBlockType blockType,
            int relativityOffset) throws VerificationException {
        lock.lock();
        try {
            Transaction tx = transactions.get(txHash);
            if (tx == null) {
                log.error("TX {} not found despite being sent to wallet", txHash);
                return;
            }
            receive(tx, block, blockType, relativityOffset);
        } finally {
            lock.unlock();
        }
        if (blockType == AbstractBlockChain.NewBlockType.BEST_CHAIN) {
            // If some keys are considered to be bad, possibly move money assigned to them now.
            // This has to run outside the wallet lock as it may trigger broadcasting of new transactions.
            maybeRotateKeys();
        }
    }

    /**
     * <p>Called when we have found a transaction (via network broadcast or otherwise) that is relevant to this wallet
     * and want to record it. Note that we <b>cannot verify these transactions at all</b>, they may spend fictional
     * coins or be otherwise invalid. They are useful to inform the user about coins they can expect to receive soon,
     * and if you trust the sender of the transaction you can choose to assume they are in fact valid and will not
     * be double spent as an optimization.</p>
     *
     * <p>This is the same as {@link Wallet#receivePending(Transaction, java.util.List)} but allows you to override the
     * {@link Wallet#isPendingTransactionRelevant(Transaction)} sanity-check to keep track of transactions that are not
     * spendable or spend our coins. This can be useful when you want to keep track of transaction confidence on
     * arbitrary transactions. Note that transactions added in this way will still be relayed to peers and appear in
     * transaction lists like any other pending transaction (even when not relevant).</p>
     */
    public void receivePending(Transaction tx, @Nullable List<Transaction> dependencies, boolean overrideIsRelevant)
            throws VerificationException {
        // Can run in a peer thread. This method will only be called if a prior call to isPendingTransactionRelevant
        // returned true, so we already know by this point that it sends coins to or from our wallet, or is a double
        // spend against one of our other pending transactions.
        lock.lock();
        try {
            tx.verify();
            // Ignore it if we already know about this transaction. Receiving a pending transaction never moves it
            // between pools.
            EnumSet<Pool> containingPools = getContainingPools(tx);
            if (!containingPools.equals(EnumSet.noneOf(Pool.class))) {
                log.debug("Received tx we already saw in a block or created ourselves: " + tx.getHashAsString());
                return;
            }
            // Repeat the check of relevancy here, even though the caller may have already done so - this is to avoid
            // race conditions where receivePending may be being called in parallel.
            if (!overrideIsRelevant && !isPendingTransactionRelevant(tx))
                return;
            if (isTransactionRisky(tx, dependencies) && !acceptRiskyTransactions)
                return;
            if (tx.getConfidence().getSource().equals(TransactionConfidence.Source.UNKNOWN)) {
                log.warn("Wallet received transaction with an unknown source. Consider tagging it!");
            }
            // If this tx spends any of our unspent outputs, mark them as spent now, then add to the pending pool. This
            // ensures that if some other client that has our keys broadcasts a spend we stay in sync. Also updates the
            // timestamp on the transaction and registers/runs event listeners.
            commitTx(tx);
        } finally {
            lock.unlock();
        }
        // maybeRotateKeys() will ignore pending transactions so we don't bother calling it here (see the comments
        // in that function for an explanation of why).
    }

    /**
     * Given a transaction and an optional list of dependencies (recursive/flattened), returns true if the given
     * transaction would be rejected by the analyzer, or false otherwise. The result of this call is independent
     * of the value of {@link #doesAcceptRiskyTransactions()}. Risky transactions yield a logged warning. If you
     * want to know the reason why a transaction is risky, create an instance of the {@link RiskAnalysis} yourself
     * using the factory returned by {@link #getRiskAnalyzer()} and use it directly.
     */
    public boolean isTransactionRisky(Transaction tx, @Nullable List<Transaction> dependencies) {
        lock.lock();
        try {
            if (dependencies == null)
                dependencies = ImmutableList.of();
            RiskAnalysis analysis = riskAnalyzer.create(this, tx, dependencies);
            RiskAnalysis.Result result = analysis.analyze();
            if (result != RiskAnalysis.Result.OK) {
                log.warn("Pending transaction {} was considered risky: {}", tx.getHashAsString(), analysis);
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * <p>Called when we have found a transaction (via network broadcast or otherwise) that is relevant to this wallet
     * and want to record it. Note that we <b>cannot verify these transactions at all</b>, they may spend fictional
     * coins or be otherwise invalid. They are useful to inform the user about coins they can expect to receive soon,
     * and if you trust the sender of the transaction you can choose to assume they are in fact valid and will not
     * be double spent as an optimization.</p>
     *
     * <p>Before this method is called, {@link Wallet#isPendingTransactionRelevant(Transaction)} should have been
     * called to decide whether the wallet cares about the transaction - if it does, then this method expects the
     * transaction and any dependencies it has which are still in the memory pool.</p>
     */
    public void receivePending(Transaction tx, @Nullable List<Transaction> dependencies)
            throws VerificationException {
        receivePending(tx, dependencies, false);
    }

    /**
     * This method is used by a {@link Peer} to find out if a transaction that has been announced is interesting,
     * that is, whether we should bother downloading its dependencies and exploring the transaction to decide how
     * risky it is. If this method returns true then {@link Wallet#receivePending(Transaction, java.util.List)}
     * will soon be called with the transactions dependencies as well.
     */
    public boolean isPendingTransactionRelevant(Transaction tx) throws ScriptException {
        lock.lock();
        try {
            // Ignore it if we already know about this transaction. Receiving a pending transaction never moves it
            // between pools.
            log.info("!!!! isPendingTransactionRelevant START " + tx.getHashAsString());
            EnumSet<Pool> containingPools = getContainingPools(tx);
            if (!containingPools.equals(EnumSet.noneOf(Pool.class))) {
                log.debug("Received tx we already saw in a block or created ourselves: " + tx.getHashAsString());
                return false;
            }
            log.info("!!!! isPendingTransactionRelevant NOT IN POOLS " + tx.getHashAsString());

            // We only care about transactions that:
            //   - Send us coins
            //   - Spend our coins
            if (!isTransactionRelevant(tx)) {
                return false;
            }
            log.info("!!!! isPendingTransactionRelevant IS RELEVANT " + tx.getHashAsString());

            if (isTransactionRisky(tx, null) && !acceptRiskyTransactions) {
                log.warn(
                        "Received transaction {} with a lock time of {}, but not configured to accept these, discarding",
                        tx.getHashAsString(), tx.getLockTime());
                return false;
            }
            log.debug("Saw relevant pending transaction " + tx.toString());
            log.info("!!!! isPendingTransactionRelevant NOT RISKY " + tx.getHashAsString());

            return true;
        } finally {
            lock.unlock();
        }
    }

    /**
     * <p>Returns true if the given transaction sends coins to any of our keys, or has inputs spending any of our outputs,
     * and if includeDoubleSpending is true, also returns true if tx has inputs that are spending outputs which are
     * not ours but which are spent by pending transactions.</p>
     *
     * <p>Note that if the tx has inputs containing one of our keys, but the connected transaction is not in the wallet,
     * it will not be considered relevant.</p>
     */
    public boolean isTransactionRelevant(Transaction tx) throws ScriptException {
        lock.lock();
        try {
            return tx.isMine(this) || tx.getValueSentFromMe(this).compareTo(BigInteger.ZERO) > 0
                    || tx.getValueSentToMe(this).compareTo(BigInteger.ZERO) > 0
                    || checkForDoubleSpendAgainstPending(tx, false);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 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 boolean checkForDoubleSpendAgainstPending(Transaction tx, boolean takeAction) {
        checkState(lock.isHeldByCurrentThread());
        // Compile a set of outpoints that are spent by tx.
        HashSet<TransactionOutPoint> outpoints = new HashSet<TransactionOutPoint>();
        for (TransactionInput input : tx.getInputs()) {
            outpoints.add(input.getOutpoint());
        }
        // Now for each pending transaction, see if it shares any outpoints with this tx.
        LinkedList<Transaction> doubleSpentTxns = Lists.newLinkedList();
        for (Transaction p : pending.values()) {
            for (TransactionInput input : p.getInputs()) {
                // This relies on the fact that TransactionOutPoint equality is defined at the protocol not object
                // level - outpoints from two different inputs that point to the same output compare the same.
                TransactionOutPoint outpoint = input.getOutpoint();
                if (outpoints.contains(outpoint)) {
                    // It does, it's a double spend against the pending pool, which makes it relevant.
                    if (!doubleSpentTxns.isEmpty() && doubleSpentTxns.getLast() == p)
                        continue;
                    doubleSpentTxns.add(p);
                }
            }
        }
        if (takeAction && !doubleSpentTxns.isEmpty()) {
            killTx(tx, doubleSpentTxns);
        }
        return !doubleSpentTxns.isEmpty();
    }

    /**
     * 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!
     */
    @Override
    public void receiveFromBlock(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType,
            int relativityOffset) throws VerificationException {
        lock.lock();

        CS.log.info("NEWTX: " + "BLOCK " + " " + tx.getHash().toString() + " " + block.getHeight() + " "
                + tx.getOutputs().size());
        for (TransactionInput input : tx.getInputs()) {
            CS.log.info("NEWTX: " + "INPUT " + " " + input.getOutpoint().getHash().toString() + " "
                    + input.getOutpoint().getIndex());
        }
        for (TransactionOutput output : tx.getOutputs()) {
            String mine = output.isMine(this) ? "MINE" : " ";
            CS.log.info(
                    "NEWTX: " + "OUTPUT" + " " + tx.getHash().toString() + " " + output.getIndex() + " " + mine);
        }

        try {
            receive(tx, block, blockType, relativityOffset);
        } finally {
            lock.unlock();
        }
        if (blockType == AbstractBlockChain.NewBlockType.BEST_CHAIN) {
            // If some keys are considered to be bad, possibly move money assigned to them now.
            // This has to run outside the wallet lock as it may trigger broadcasting of new transactions.
            maybeRotateKeys();
        }
    }

    private void receive(Transaction tx, StoredBlock block, BlockChain.NewBlockType blockType, int relativityOffset)
            throws VerificationException {
        // Runs in a peer thread.
        checkState(lock.isHeldByCurrentThread());
        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.info("!!!! receive START " + tx.getHashAsString());

        log.info("Received tx{} for {} BTC: {} [{}] in block {}", sideChain ? " on a side chain" : "",
                bitcoinValueToFriendlyString(valueDifference), tx.getHashAsString(), relativityOffset,
                block != null ? block.getHeader().getHash() : "(unit test)");

        /* CSPK-mike START */
        // Looking for CoinSpark assets in the transaction
        if (bestChain) {

            int blockHeight = 0;
            if (block != null) {
                blockHeight = block.getHeight();
                CS.log.info("New tx " + tx.getHash().toString() + " in block " + blockHeight);
            }
            log.info("!!!! receive HIT!!! " + tx.getHashAsString());

            CSTransactionAssets txAssets = new CSTransactionAssets(tx);
            txAssets.updateAssetBalances(this, blockHeight, CS.getInputAssetBalances(tx));

            /*
            Look for payment reference and post CSEvent if found
            */
            final int FBHCHAIN_START_BLOCK = 312500; // July 26th 2014
            // optimise, only check from period when assets were first being created
            if (blockHeight > FBHCHAIN_START_BLOCK) {
                log.info("!!!! Extracting CoinSpark payment reference from a transaction...");

                byte[] txnMetaData = null;
                //int metadataOutput=0;
                //int count=0;
                for (TransactionOutput output : tx.getOutputs()) {

                    // TRANSACTION_PAYMENT_REFERENCE_DETECTED
                    byte[] scriptBytes = output.getScriptBytes();
                    if (!CoinSparkBase.scriptIsRegular(scriptBytes)) {
                        txnMetaData = CoinSparkBase.scriptToMetadata(scriptBytes);
                        break;
                    }
                }

                if (txnMetaData != null) {
                    CoinSparkPaymentRef paymentRef = new CoinSparkPaymentRef();
                    if (paymentRef.decode(txnMetaData)) {
                        log.info("!!!! Found Payment Ref: " + paymentRef.toString());
                        HashMap<String, Long> map = new HashMap<String, Long>();
                        map.put(tx.getHashAsString(), paymentRef.getRef());
                        CSEventBus.INSTANCE.postAsyncEvent(CSEventType.TRANSACTION_PAYMENT_REFERENCE_RECEIVED, map);
                    }
                }
            }
        }
        /* CSPK-mike END */

        // If the transaction is being replayed no need to add it to the wallet again.
        Transaction diagnosticTx = tx;
        if (spent.containsKey(txHash)) {
            diagnosticTx = spent.get(txHash);
        }
        if (unspent.containsKey(txHash)) {
            diagnosticTx = unspent.get(txHash);
        }

        if (dead.containsKey(txHash)) {
            diagnosticTx = dead.get(txHash);
        }

        boolean isReplay = ((spent.containsKey(txHash) || unspent.containsKey(txHash) || dead.containsKey(txHash))
                && bestChain) && diagnosticTx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING
                && (diagnosticTx.getConfidence().getAppearedAtChainHeight() > lastBlockSeenHeight);

        if (isReplay) {
            log.debug("Replay diagnostic for tx = " + txHash);
            log.debug("  spent.containsKey(txHash) = " + spent.containsKey(txHash));
            log.debug("  unspent.containsKey(txHash) = " + unspent.containsKey(txHash));
            log.debug("  dead.containsKey(txHash) = " + dead.containsKey(txHash));
            log.debug("  diagnosticTx.getConfidence().getConfidenceType() = "
                    + diagnosticTx.getConfidence().getConfidenceType());
            if (diagnosticTx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING) {
                log.debug("  diagnosticTx.getConfidence().getAppearedAtChainHeight() = "
                        + diagnosticTx.getConfidence().getAppearedAtChainHeight());
            }
            log.debug("  lastBlockSeenHeight = " + lastBlockSeenHeight);
            log.debug("  bestChain = " + bestChain);

            // We know the tx appears in the chain in the future (compared to
            // now) and it is not a reorg so can ignore it.
            // This happens on replay.
            log.debug("Received a tx '" + txHash.toString() + "' which is a replay so ignoring.");
            return;
        }
        onWalletChangedSuppressions++;

        // 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 tmp = transactions.get(tx.getHash());
            if (tmp != null)
                tx = tmp;
        }

        boolean wasPending = pending.remove(txHash) != null;
        if (wasPending)
            log.info("  <-pending");

        if (bestChain) {
            if (wasPending) {
                // Was pending and is now confirmed. Disconnect the outputs in case we spent any already: they will be
                // re-connected by processTxFromBestChain below.
                for (TransactionOutput output : tx.getOutputs()) {
                    final TransactionInput spentBy = output.getSpentBy();
                    if (spentBy != null)
                        spentBy.disconnect();
                }
            }

            processTxFromBestChain(tx, wasPending);
        } else {
            checkState(sideChain);
            // Transactions that appear in a side chain will have that appearance recorded below - we assume that
            // some miners are also trying to include the transaction into the current best chain too, so let's treat
            // it as pending, except we don't need to do any risk analysis on it.
            if (wasPending) {
                // Just put it back in without touching the connections or confidence.
                addWalletTransaction(Pool.PENDING, tx);
                log.info("  ->pending");
            } else {
                // Ignore the case where a tx appears on a side chain at the same time as the best chain (this is
                // quite normal and expected).
                Sha256Hash hash = tx.getHash();
                if (!unspent.containsKey(hash) && !spent.containsKey(hash)) {
                    // Otherwise put it (possibly back) into pending.
                    // Committing it updates the spent flags and inserts into the pool as well.
                    commitTx(tx);
                }
            }
        }

        if (block != null) {
            // Mark the tx as appearing in this block so we can find it later after a re-org. This also tells the tx
            // confidence object about the block and sets its work done/depth appropriately.
            tx.setBlockAppearance(block, bestChain, relativityOffset);
            if (bestChain) {
                // Don't notify this tx of work done in notifyNewBestBlock which will be called immediately after
                // this method has been called by BlockChain for all relevant transactions. Otherwise we'd double
                // count.
                ignoreNextNewBlock.add(txHash);
            }
        }

        onWalletChangedSuppressions--;

        // Side chains don't affect confidence.
        if (bestChain) {
            // notifyNewBestBlock will be invoked next and will then call maybeQueueOnWalletChanged for us.
            confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
        } else {
            maybeQueueOnWalletChanged();
        }

        // Inform anyone interested that we have received or sent coins but only if:
        //  - This is not due to a re-org.
        //  - The coins appeared on the best chain.
        //  - We did in fact receive some new money.
        //  - We have not already informed the user about the coins when we received the tx broadcast, or for our
        //    own spends. If users want to know when a broadcast tx becomes confirmed, they need to use tx confidence
        //    listeners.
        if (!insideReorg && bestChain) {
            BigInteger newBalance = getBalance(); // This is slow.
            log.info("Balance is now: " + bitcoinValueToFriendlyString(newBalance));
            if (!wasPending) {
                int diff = valueDifference.compareTo(BigInteger.ZERO);
                // We pick one callback based on the value difference, though a tx can of course both send and receive
                // coins from the wallet.
                if (diff > 0) {
                    queueOnCoinsReceived(tx, prevBalance, newBalance);
                } else if (diff < 0) {
                    queueOnCoinsSent(tx, prevBalance, newBalance);
                }
            }
            checkBalanceFuturesLocked(newBalance);
        }

        informConfidenceListenersIfNotReorganizing();
        checkState(isConsistent());

        //saveNow();
    }

    private void informConfidenceListenersIfNotReorganizing() {
        if (insideReorg)
            return;
        for (Map.Entry<Transaction, TransactionConfidence.Listener.ChangeReason> entry : confidenceChanged
                .entrySet()) {
            final Transaction tx = entry.getKey();
            tx.getConfidence().queueListeners(entry.getValue());
            queueOnTransactionConfidenceChanged(tx);
        }
        confidenceChanged.clear();
    }

    /**
     * <p>Called by the {@link BlockChain} when a new block on the best chain is seen, AFTER relevant wallet
     * transactions are extracted and sent to us UNLESS the new block caused a re-org, in which case this will
     * not be called (the {@link Wallet#reorganize(StoredBlock, java.util.List, java.util.List)} method will
     * call this one in that case).</p>
     * <p/>
     * <p>Used to update confidence data in each transaction and last seen block hash. Triggers auto saving.
     * Invokes the onWalletChanged event listener if there were any affected transactions.</p>
     */
    public void notifyNewBestBlock(StoredBlock block) throws VerificationException {
        // Check to see if this block has been seen before.
        Sha256Hash newBlockHash = block.getHeader().getHash();
        if (newBlockHash.equals(getLastBlockSeenHash()))
            return;
        lock.lock();
        try {

            // Store the new block hash if it is the successor of the current lastSeenBlock.
            // Otherwise keep the previous.
            // This is to avoid a gap in the blocks in the blockchain that the wallet has seen.
            // You can set it to backwards in time (e.g. at the start a replay) as this is safe.
            int lastBlockSeenHeight = getLastBlockSeenHeight();
            if (lastBlockSeenHeight == -1
                    || (lastBlockSeenHeight >= 0 && (block.getHeight() <= lastBlockSeenHeight + 1))) {
                setLastBlockSeenHash(newBlockHash);
                setLastBlockSeenHeight(block.getHeight());
                setLastBlockSeenTimeSecs(block.getHeader().getTimeSeconds());
            }

            if (CS.log != null) {
                CS.log.info("New block " + block.getHeight() + ": " + block.getHeader().getHashAsString());
            }

            // TODO: Clarify the code below.
            // Notify all the BUILDING transactions of the new block.
            // This is so that they can update their work done and depth.
            Set<Transaction> transactions = getTransactions(true);
            for (Transaction tx : transactions) {
                if (ignoreNextNewBlock.contains(tx.getHash())) {
                    // tx was already processed in receive() due to it appearing in this block, so we don't want to
                    // notify the tx confidence of work done twice, it'd result in miscounting.
                    ignoreNextNewBlock.remove(tx.getHash());
                } else if (tx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING) {
                    tx.getConfidence().notifyWorkDone(block.getHeader());
                    confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
                }
            }

            informConfidenceListenersIfNotReorganizing();
            maybeQueueOnWalletChanged();
            // Coalesce writes to avoid throttling on disk access when catching up with the chain.
            //saveLater();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Handle when a transaction becomes newly active on the best chain, either due to receiving a new block or a
     * re-org. Places the tx into the right pool, handles coinbase transactions, handles double-spends and so on.
     */
    private void processTxFromBestChain(Transaction tx, boolean forceAddToPool) throws VerificationException {
        checkState(lock.isHeldByCurrentThread());
        checkState(!pending.containsKey(tx.getHash()));

        // This TX may spend our existing outputs even though it was not pending. This can happen in unit
        // tests, if keys are moved between wallets, if we're catching up to the chain given only a set of keys,
        // or if a dead coinbase transaction has moved back onto the main chain.
        boolean isDeadCoinbase = tx.isCoinBase() && dead.containsKey(tx.getHash());
        if (isDeadCoinbase) {
            // There is a dead coinbase tx being received on the best chain. A coinbase tx is made dead when it moves
            // to a side chain but it can be switched back on a reorg and 'resurrected' back to spent or unspent.
            // So take it out of the dead pool.
            log.info("  coinbase tx {} <-dead: confidence {}", tx.getHashAsString(),
                    tx.getConfidence().getConfidenceType().name());
            dead.remove(tx.getHash());
        }

        // Update tx and other unspent/pending transactions by connecting inputs/outputs.
        updateForSpends(tx, true);

        // Now make sure it ends up in the right pool. Also, handle the case where this TX is double-spending
        // against our pending transactions. Note that a tx may double spend our pending transactions and also send
        // us money/spend our money.
        boolean hasOutputsToMe = tx.getValueSentToMe(this, true).compareTo(BigInteger.ZERO) > 0;
        if (hasOutputsToMe) {
            // Needs to go into either unspent or spent (if the outputs were already spent by a pending tx).
            if (tx.isEveryOwnedOutputSpent(this)) {
                log.info("  tx {} ->spent (by pending)", tx.getHashAsString());
                addWalletTransaction(Pool.SPENT, tx);
            } else {
                log.info("  tx {} ->unspent", tx.getHashAsString());
                addWalletTransaction(Pool.UNSPENT, tx);
            }
        } else if (tx.getValueSentFromMe(this).compareTo(BigInteger.ZERO) > 0) {
            // Didn't send us any money, but did spend some. Keep it around for record keeping purposes.
            log.info("  tx {} ->spent", tx.getHashAsString());
            addWalletTransaction(Pool.SPENT, tx);
        } else if (forceAddToPool) {
            // Was manually added to pending, so we should keep it to notify the user of confidence information
            log.info("  tx {} ->spent (manually added)", tx.getHashAsString());
            addWalletTransaction(Pool.SPENT, tx);
        }

        checkForDoubleSpendAgainstPending(tx, true);
    }

    /**
     * <p>Updates the wallet by checking if this TX spends any of our outputs, and marking them as spent if so. If
     * fromChain is true, also checks to see if any pending transaction spends outputs of this transaction and marks
     * the spent flags appropriately.</p>
     *
     * <p>It can be called in two contexts. One is when we receive a transaction on the best chain but it wasn't pending,
     * this most commonly happens when we have a set of keys but the wallet transactions were wiped and we are catching
     * up with the block chain. It can also happen if a block includes a transaction we never saw at broadcast time.
     * If this tx double spends, it takes precedence over our pending transactions and the pending tx goes dead.</p>
     *
     * <p>The other context it can be called is from {@link Wallet#receivePending(Transaction, java.util.List)},
     * ie we saw a tx be broadcast or one was submitted directly that spends our own coins. If this tx double spends
     * it does NOT take precedence because the winner will be resolved by the miners - we assume that our version will
     * win, if we are wrong then when a block appears the tx will go dead.</p>
     *
     * @param tx The transaction which is being updated.
     * @param fromChain If true, the tx appeared on the current best chain, if false it was pending.
     */
    private void updateForSpends(Transaction tx, boolean fromChain) throws VerificationException {
        checkState(lock.isHeldByCurrentThread());
        if (fromChain)
            checkState(!pending.containsKey(tx.getHash()));
        for (TransactionInput input : tx.getInputs()) {
            TransactionInput.ConnectionResult result = input.connect(unspent,
                    TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
            if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
                // Not found in the unspent map. Try again with the spent map.
                result = input.connect(spent, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
                if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
                    // Not found in the unspent and spent maps. Try again with the pending map.
                    result = input.connect(pending, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
                    if (result == TransactionInput.ConnectionResult.NO_SUCH_TX) {
                        // Doesn't spend any of our outputs or is coinbase.
                        continue;
                    }
                }
            }

            if (result == TransactionInput.ConnectionResult.ALREADY_SPENT) {
                if (fromChain) {
                    // Double spend from chain: this will be handled later by checkForDoubleSpendAgainstPending.
                    log.warn("updateForSpends: saw double spend from chain, handling later.");
                } else {
                    // We saw two pending transactions that double spend each other. We don't know which will win.
                    // This can happen in the case of bad network nodes that mutate transactions. Do a hex dump
                    // so the exact nature of the mutation can be examined.
                    log.warn("Saw two pending transactions double spend each other");
                    log.warn("  offending input is input {}", tx.getInputs().indexOf(input));
                    log.warn("{}: {}", tx.getHash(), new String(Hex.encode(tx.unsafeBitcoinSerialize())));
                    Transaction other = input.getConnectedOutput().getSpentBy().getParentTransaction();
                    log.warn("{}: {}", other.getHash(), new String(Hex.encode(tx.unsafeBitcoinSerialize())));
                }
            } 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 = checkNotNull(input.getOutpoint().fromTx);
                log.info("  marked {} as spent", input.getOutpoint());
                maybeMovePool(connected, "prevtx");
            }
        }
        // Now check each output and see if there is a pending transaction which spends it. This shouldn't normally
        // ever occur because we expect transactions to arrive in temporal order, but this assumption can be violated
        // when we receive a pending transaction from the mempool that is relevant to us, which spends coins that we
        // didn't see arrive on the best chain yet. For instance, because of a chain replay or because of our keys were
        // used by another wallet somewhere else.
        if (fromChain) {
            for (Transaction pendingTx : pending.values()) {
                for (TransactionInput input : pendingTx.getInputs()) {
                    TransactionInput.ConnectionResult result = input.connect(tx,
                            TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
                    // This TX is supposed to have just appeared on the best chain, so its outputs should not be marked
                    // as spent yet. If they are, it means something is happening out of order.
                    //
                    // (Commented out as this can happen if you have a pending tx which spends this and you are doing a replay.
                    // You get the spending pending tx before the one from the block).
                    // checkState(result != TransactionInput.ConnectionResult.ALREADY_SPENT);
                    if (result == TransactionInput.ConnectionResult.SUCCESS) {
                        log.info("Connected pending tx input {}:{}", pendingTx.getHashAsString(),
                                pendingTx.getInputs().indexOf(input));
                    }
                }
                // If the transactions outputs are now all spent, it will be moved into the spent pool by the
                // processTxFromBestChain method.
            }
        }
    }

    private void killCoinbase(Transaction coinbase) {
        log.warn("Coinbase killed by re-org: {}", coinbase.getHashAsString());
        coinbase.getConfidence().setOverridingTransaction(null);
        confidenceChanged.put(coinbase, TransactionConfidence.Listener.ChangeReason.TYPE);
        final Sha256Hash hash = coinbase.getHash();
        pending.remove(hash);
        unspent.remove(hash);
        spent.remove(hash);
        addWalletTransaction(Pool.DEAD, coinbase);
        // TODO: Properly handle the recursive nature of killing transactions here.
    }

    // Updates the wallet when a double spend occurs. overridingTx/overridingInput can be null for the case of coinbases
    private void killTx(Transaction overridingTx, List<Transaction> killedTx) {
        for (Transaction tx : killedTx) {
            log.warn("Saw double spend from chain override pending tx {}", tx.getHashAsString());
            log.warn("  <-pending ->dead   killed by {}", overridingTx.getHashAsString());
            log.warn("Disconnecting each input and moving connected transactions.");
            pending.remove(tx.getHash());
            addWalletTransaction(Pool.DEAD, tx);
            for (TransactionInput deadInput : tx.getInputs()) {
                Transaction connected = deadInput.getOutpoint().fromTx;
                if (connected == null)
                    continue;
                deadInput.disconnect();
                maybeMovePool(connected, "kill");
            }
            tx.getConfidence().setOverridingTransaction(overridingTx);
            confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
        }
        log.warn("Now attempting to connect the inputs of the overriding transaction.");
        for (TransactionInput input : overridingTx.getInputs()) {
            TransactionInput.ConnectionResult result = input.connect(unspent,
                    TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT);
            if (result == TransactionInput.ConnectionResult.SUCCESS) {
                maybeMovePool(input.getOutpoint().fromTx, "kill");
            } else {
                result = input.connect(spent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT);
                if (result == TransactionInput.ConnectionResult.SUCCESS) {
                    maybeMovePool(input.getOutpoint().fromTx, "kill");
                }
            }
        }
        // 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.
     * If the owned transactions outputs are not all marked as spent, and it's in the spent map, move it.
     */
    private void maybeMovePool(Transaction tx, String context) {
        checkState(lock.isHeldByCurrentThread());
        if (tx.isEveryOwnedOutputSpent(this)) {
            // There's nothing left I can spend in this transaction.
            if (unspent.remove(tx.getHash()) != null) {
                if (log.isInfoEnabled()) {
                    log.info("  {} {} <-unspent ->spent", tx.getHashAsString(), context);
                }
                spent.put(tx.getHash(), tx);
            }
        } else {
            if (spent.remove(tx.getHash()) != null) {
                if (log.isInfoEnabled()) {
                    log.info("  {} {} <-spent ->unspent", tx.getHashAsString(), context);
                }
                unspent.put(tx.getHash(), tx);
            }
        }
    }

    /**
     * Adds an event listener object. Methods on this object are called when something interesting happens,
     * like receiving money. Runs the listener methods in the user thread.
     */
    public void addEventListener(WalletEventListener listener) {
        addEventListener(listener, Threading.USER_THREAD);
    }

    /**
     * Adds an event listener object. Methods on this object are called when something interesting happens,
     * like receiving money. The listener is executed by the given executor.
     */
    public void addEventListener(WalletEventListener listener, Executor executor) {
        eventListeners.add(new ListenerRegistration<WalletEventListener>(listener, executor));
    }

    /**
     * Removes the given event listener object. Returns true if the listener was removed, false if that listener
     * was never added.
     */
    public boolean removeEventListener(WalletEventListener listener) {
        return ListenerRegistration.removeFromList(listener, eventListeners);
    }

    /**
     * Calls {@link Wallet#commitTx} if tx is not already in the pending pool
     *
     * @return true if the tx was added to the wallet, or false if it was already in the pending pool
     */
    public boolean maybeCommitTx(Transaction tx) throws VerificationException {
        tx.verify();
        lock.lock();
        try {
            log.info("!!!! commit START!!! " + tx.getHashAsString());
            if (pending.containsKey(tx.getHash()))
                return false;
            log.info("commitTx of {}", tx.getHashAsString());
            BigInteger balance = getBalance();
            tx.setUpdateTime(Utils.now());
            // Mark the outputs we're spending as spent so we won't try and use them in future creations. This will also
            // move any transactions that are now fully spent to the spent map so we can skip them when creating future
            // spends.
            updateForSpends(tx, false);
            // Add to the pending pool. It'll be moved out once we receive this transaction on the best chain.
            // This also registers txConfidenceListener so wallet listeners get informed.
            log.info("->pending: {}", tx.getHashAsString());
            tx.getConfidence().setConfidenceType(ConfidenceType.PENDING);
            confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
            addWalletTransaction(Pool.PENDING, tx);

            /* CSPK-mike START */

            // Looking for assets in pending transaction
            CS.log.info("New tx " + tx.getHash().toString() + " in memory pool");

            log.info("!!!! commit HIT!!! " + tx.getHashAsString());
            CSTransactionAssets txAssets = new CSTransactionAssets(tx);
            txAssets.updateAssetBalances(this, 0, CS.getInputAssetBalances(tx));
            CS.addToRecentSends(tx);

            /* CSPK-mike END */

            try {
                BigInteger valueSentFromMe = tx.getValueSentFromMe(this);
                BigInteger valueSentToMe = tx.getValueSentToMe(this);
                BigInteger newBalance = balance.add(valueSentToMe).subtract(valueSentFromMe);
                if (valueSentToMe.compareTo(BigInteger.ZERO) > 0) {
                    checkBalanceFuturesLocked(null);
                    queueOnCoinsReceived(tx, balance, newBalance);
                }
                if (valueSentFromMe.compareTo(BigInteger.ZERO) > 0)
                    queueOnCoinsSent(tx, balance, newBalance);

                maybeQueueOnWalletChanged();
            } catch (ScriptException e) {
                // Cannot happen as we just created this transaction ourselves.
                throw new RuntimeException(e);
            }

            checkState(isConsistent());
            informConfidenceListenersIfNotReorganizing();
            //saveNow();
        } finally {
            lock.unlock();
        }
        return true;
    }

    /**
     * <p>Updates the wallet with the given transaction: puts it into the pending pool, sets the spent flags and runs
     * the onCoinsSent/onCoinsReceived event listener. Used in two situations:</p>
     *
     * <ol>
     *     <li>When we have just successfully transmitted the tx we created to the network.</li>
     *     <li>When we receive a pending transaction that didn't appear in the chain yet, and we did not create it.</li>
     * </ol>
     *
     * <p>Triggers an auto save.</p>
     */
    public void commitTx(Transaction tx) throws VerificationException {
        checkArgument(maybeCommitTx(tx), "commitTx called on the same transaction twice");
    }

    /**
     * Returns a set of all transactions in the wallet.
     * @param includeDead     If true, transactions that were overridden by a double spend are included.
     */
    public Set<Transaction> getTransactions(boolean includeDead) {
        lock.lock();
        try {
            Set<Transaction> all = new HashSet<Transaction>();
            all.addAll(unspent.values());
            all.addAll(spent.values());
            all.addAll(pending.values());
            if (includeDead)
                all.addAll(dead.values());
            return all;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a set of all WalletTransactions in the wallet.
     */
    public Iterable<WalletTransaction> getWalletTransactions() {
        lock.lock();
        try {
            Set<WalletTransaction> all = new HashSet<WalletTransaction>();
            addWalletTransactionsToSet(all, Pool.UNSPENT, unspent.values());
            addWalletTransactionsToSet(all, Pool.SPENT, spent.values());
            addWalletTransactionsToSet(all, Pool.DEAD, dead.values());
            addWalletTransactionsToSet(all, Pool.PENDING, pending.values());
            return all;
        } finally {
            lock.unlock();
        }
    }

    private static void addWalletTransactionsToSet(Set<WalletTransaction> txs, Pool poolType,
            Collection<Transaction> pool) {
        for (Transaction tx : pool) {
            txs.add(new WalletTransaction(poolType, tx));
        }
    }

    /**
     * Adds a transaction that has been associated with a particular wallet pool. This is intended for usage by
     * deserialization code, such as the {@link WalletProtobufSerializer} class. It isn't normally useful for
     * applications. It does not trigger auto saving.
     */
    public void addWalletTransaction(WalletTransaction wtx) {
        lock.lock();
        try {
            addWalletTransaction(wtx.getPool(), wtx.getTransaction());
        } finally {
            lock.unlock();
        }
    }

    /**
     * Adds the given transaction to the given pools and registers a confidence change listener on it.
     */
    private void addWalletTransaction(Pool pool, Transaction tx) {
        checkState(lock.isHeldByCurrentThread());
        transactions.put(tx.getHash(), tx);
        switch (pool) {
        case UNSPENT:
            unspent.put(tx.getHash(), tx);
            break;
        case SPENT:
            spent.put(tx.getHash(), tx);
            break;
        case PENDING:
            pending.put(tx.getHash(), tx);
            break;
        case DEAD:
            dead.put(tx.getHash(), tx);
            break;
        default:
            throw new RuntimeException("Unknown wallet transaction type " + pool);
        }
        // This is safe even if the listener has been added before, as TransactionConfidence ignores duplicate
        // registration requests. That makes the code in the wallet simpler.
        tx.getConfidence().addEventListener(txConfidenceListener, Threading.SAME_THREAD);
    }

    /**
     * Returns all non-dead, active transactions ordered by recency.
     */
    public List<Transaction> getTransactionsByTime() {
        return getRecentTransactions(0, false);
    }

    /**
     * Returns an list of N transactions, ordered by increasing age. Transactions on side chains are not included.
     * Dead transactions (overridden by double spends) are optionally included. <p>
     * <p/>
     * Note: the current implementation is O(num transactions in wallet). Regardless of how many transactions are
     * requested, the cost is always the same. In future, requesting smaller numbers of transactions may be faster
     * depending on how the wallet is implemented (eg if backed by a database).
     */
    public List<Transaction> getRecentTransactions(int numTransactions, boolean includeDead) {
        lock.lock();
        try {
            checkArgument(numTransactions >= 0);
            // Firstly, put all transactions into an array.
            int size = getPoolSize(Pool.UNSPENT) + getPoolSize(Pool.SPENT) + getPoolSize(Pool.PENDING);
            if (numTransactions > size || numTransactions == 0) {
                numTransactions = size;
            }
            ArrayList<Transaction> all = new ArrayList<Transaction>(getTransactions(includeDead));
            // Order by date.
            Collections.sort(all, Collections.reverseOrder(new Comparator<Transaction>() {
                public int compare(Transaction t1, Transaction t2) {
                    return t1.getUpdateTime().compareTo(t2.getUpdateTime());
                }
            }));
            if (numTransactions == all.size()) {
                return all;
            } else {
                all.subList(numTransactions, all.size()).clear();
                return all;
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a transaction object given its hash, if it exists in this wallet, or null otherwise.
     */
    @Nullable
    public Transaction getTransaction(Sha256Hash hash) {
        lock.lock();
        try {
            return transactions.get(hash);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Deletes transactions which appeared above the given block height from the wallet, but does not touch the keys.
     * This is useful if you have some keys and wish to replay the block chain into the wallet in order to pick them up.
     */
    public void clearTransactions(int fromHeight) {
        lock.lock();
        try {
            if (fromHeight == 0) {
                unspent.clear();
                spent.clear();
                pending.clear();
                dead.clear();
                transactions.clear();

                //saveLater();
            } else {
                throw new UnsupportedOperationException();
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Clean up the wallet. Currently, it only removes risky pending transaction from the wallet and only if their
     * outputs have not been spent.
     */
    public void cleanup() {
        lock.lock();
        try {
            boolean dirty = false;
            for (Iterator<Transaction> i = pending.values().iterator(); i.hasNext();) {
                Transaction tx = i.next();
                if (isTransactionRisky(tx, null) && !acceptRiskyTransactions) {
                    log.debug("Found risky transaction {} in wallet during cleanup.", tx.getHashAsString());
                    if (!tx.isAnyOutputSpent()) {
                        tx.disconnectInputs();
                        i.remove();
                        transactions.remove(tx.getHash());
                        dirty = true;
                        log.info("Removed transaction {} from pending pool during cleanup.", tx.getHashAsString());
                    } else {
                        log.info(
                                "Cannot remove transaction {} from pending pool during cleanup, as it's already spent partially.",
                                tx.getHashAsString());
                    }
                }
            }
            if (dirty) {
                checkState(isConsistent());
                saveLater();
            }
        } finally {
            lock.unlock();
        }
    }

    EnumSet<Pool> getContainingPools(Transaction tx) {
        lock.lock();
        try {
            EnumSet<Pool> result = EnumSet.noneOf(Pool.class);
            Sha256Hash txHash = tx.getHash();
            if (unspent.containsKey(txHash)) {
                result.add(Pool.UNSPENT);
            }
            if (spent.containsKey(txHash)) {
                result.add(Pool.SPENT);
            }
            if (pending.containsKey(txHash)) {
                result.add(Pool.PENDING);
            }
            if (dead.containsKey(txHash)) {
                result.add(Pool.DEAD);
            }
            return result;
        } finally {
            lock.unlock();
        }
    }

    int getPoolSize(WalletTransaction.Pool pool) {
        lock.lock();
        try {
            switch (pool) {
            case UNSPENT:
                return unspent.size();
            case SPENT:
                return spent.size();
            case PENDING:
                return pending.size();
            case DEAD:
                return dead.size();
            }
            throw new RuntimeException("Unreachable");
        } finally {
            lock.unlock();
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    //  SEND APIS
    //
    //////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /** A SendResult is returned to you as part of sending coins to a recipient. */
    public static class SendResult {
        /** The Bitcoin transaction message that moves the money. */
        public Transaction tx;
        /** A future that will complete once the tx message has been successfully broadcast to the network. */
        public ListenableFuture<Transaction> broadcastComplete;
    }

    /**
     * A SendRequest gives the wallet information about precisely how to send money to a recipient or set of recipients.
     * Static methods are provided to help you create SendRequests and there are a few helper methods on the wallet that
     * just simplify the most common use cases. You may wish to customize a SendRequest if you want to attach a fee or
     * modify the change address.
     */
    public static class SendRequest {
        /**
         * <p>A transaction, probably incomplete, that describes the outline of what you want to do. This typically will
         * mean it has some outputs to the intended destinations, but no inputs or change address (and therefore no
         * fees) - the wallet will calculate all that for you and update tx later.</p>
         *
         * <p>Be careful when adding outputs that you check the min output value
         * ({@link TransactionOutput#getMinNonDustValue(BigInteger)}) to avoid the whole transaction being rejected
         * because one output is dust.</p>
         *
         * <p>If there are already inputs to the transaction, make sure their out point has a connected output,
         * otherwise their value will be added to fee.  Also ensure they are either signed or are spendable by a wallet
         * key, otherwise the behavior of {@link Wallet#completeTx(Wallet.SendRequest)} is undefined (likely
         * RuntimeException).</p>
         */
        public Transaction tx;

        /**
         * When emptyWallet is set, all coins selected by the coin selector are sent to the first output in tx
         * (its value is ignored and set to {@link com.google.bitcoin.core.Wallet#getBalance()} - the fees required
         * for the transaction). Any additional outputs are removed.
         */
        public boolean emptyWallet = false;

        /**
         * "Change" means the difference between the value gathered by a transactions inputs (the size of which you
         * don't really control as it depends on who sent you money), and the value being sent somewhere else. The
         * change address should be selected from this wallet, normally. <b>If null this will be chosen for you.</b>
         */
        public Address changeAddress = null;

        /**
         * <p>A transaction can have a fee attached, which is defined as the difference between the input values
         * and output values. Any value taken in that is not provided to an output can be claimed by a miner. This
         * is how mining is incentivized in later years of the Bitcoin system when inflation drops. It also provides
         * a way for people to prioritize their transactions over others and is used as a way to make denial of service
         * attacks expensive.</p>
         *
         * <p>This is a constant fee (in satoshis) which will be added to the transaction. It is recommended that it be
         * at least {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE} if it is set, as default reference clients will
         * otherwise simply treat the transaction as if there were no fee at all.</p>
         *
         * <p>Once {@link Wallet#completeTx(com.google.bitcoin.core.Wallet.SendRequest)} is called, this is set to the
         * value of the fee that was added.</p>
         *
         * <p>You might also consider adding a {@link SendRequest#feePerKb} to set the fee per kb of transaction size
         * (rounded down to the nearest kb) as that is how transactions are sorted when added to a block by miners.</p>
         */
        public BigInteger fee = null;

        /**
         * <p>A transaction can have a fee attached, which is defined as the difference between the input values
         * and output values. Any value taken in that is not provided to an output can be claimed by a miner. This
         * is how mining is incentivized in later years of the Bitcoin system when inflation drops. It also provides
         * a way for people to prioritize their transactions over others and is used as a way to make denial of service
         * attacks expensive.</p>
         *
         * <p>This is a dynamic fee (in satoshis) which will be added to the transaction for each kilobyte in size
         * including the first. This is useful as as miners usually sort pending transactions by their fee per unit size
         * when choosing which transactions to add to a block. Note that, to keep this equivalent to the reference
         * client definition, a kilobyte is defined as 1000 bytes, not 1024.</p>
         *
         * <p>You might also consider using a {@link SendRequest#fee} to set the fee added for the first kb of size.</p>
         */
        public BigInteger feePerKb = DEFAULT_FEE_PER_KB;

        /**
         * If you want to modify the default fee for your entire app without having to change each SendRequest you make,
         * you can do it here. This is primarily useful for unit tests.
         */
        public static BigInteger DEFAULT_FEE_PER_KB = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;

        /**
         * <p>Requires that there be enough fee for a default reference client to at least relay the transaction.
         * (ie ensure the transaction will not be outright rejected by the network). Defaults to true, you should
         * only set this to false if you know what you're doing.</p>
         *
         * <p>Note that this does not enforce certain fee rules that only apply to transactions which are larger than
         * 26,000 bytes. If you get a transaction which is that large, you should set a fee and feePerKb of at least
         * {@link Transaction#REFERENCE_DEFAULT_MIN_TX_FEE}.</p>
         */
        public boolean ensureMinRequiredFee = true;

        /**
         * The AES key to use to decrypt the private keys before signing.
         * If null then no decryption will be performed and if decryption is required an exception will be thrown.
         * You can get this from a password by doing wallet.getKeyCrypter().deriveKey(password).
         */
        public KeyParameter aesKey = null;

        /**
         * If not null, the {@link com.google.bitcoin.wallet.CoinSelector} to use instead of the wallets default. Coin selectors are
         * responsible for choosing which transaction outputs (coins) in a wallet to use given the desired send value
         * amount.
         */
        public CoinSelector coinSelector = null;

        // Tracks if this has been passed to wallet.completeTx already: just a safety check.
        private boolean completed;

        private SendRequest() {
        }

        /**
         * <p>Creates a new SendRequest to the given address for the given value.</p>
         *
         * <p>Be very careful when value is smaller than {@link Transaction#MIN_NONDUST_OUTPUT} as the transaction will
         * likely be rejected by the network in this case.</p>
         */

        public static SendRequest to(Address destination, BigInteger value) {
            SendRequest req = new SendRequest();
            final NetworkParameters parameters = destination.getParameters();
            checkNotNull(parameters, "Address is for an unknown network");
            req.tx = new Transaction(parameters);
            req.tx.addOutput(value, destination);
            return req;
        }

        /* CSPK-mike START */

        // CoinSpark assets extensions of SendRequest class
        private class CSAssetTransfer {
            int assetID;
            BigInteger value;
            int first;
            int count;

            public CSAssetTransfer() {
            }

            public CSAssetTransfer(int OutputID, int AssetID, BigInteger Value, int Split) {
                first = OutputID;
                assetID = AssetID;
                value = Value;
                count = Split;
            }

        }

        private CoinSparkPaymentRef paymentRef = null;
        private CoinSparkMessagePart[] messageParts = null;
        private String[] deliveryServers = null;
        private int KeepSeconds = 604800; // By default servers keep message for one week so try that. TODO: Use constant
        private CSMessage messageToCreate = null;
        private CSNonce createNonce = null;
        private CoinSparkMessage message = null;

        private ArrayList<CSAssetTransfer> assetTransfers = null;
        private int[] assetsEncoded;
        private BigInteger assetFee = BigInteger.ZERO;
        CoinSparkTransferList transfers;

        private void addAssetTransfer(int OutputID, int AssetID, BigInteger Value, int split) {
            assetTransfers.add(new CSAssetTransfer(OutputID, AssetID, Value, split));
        }

        public void setMessage(CoinSparkMessagePart[] MessageParts, String[] DeliveryServers) {
            messageParts = MessageParts;
            deliveryServers = DeliveryServers;
        }

        public void setPaymentRef(CoinSparkPaymentRef PaymentRef) {
            paymentRef = PaymentRef;
        }

        public CoinSparkPaymentRef getPaymentRef() {
            return paymentRef;
        }

        public CoinSparkMessagePart[] getMessageParts() {
            return messageParts;
        }

        /**
         * Creates new asset transfer transaction.
         * 
         * @param destination - destination address
         * @param value - BTC value (in satoshis). Set by the wallet - set it to minimal anti-dust value or so
         * @param assetID - Asset ID to send
         * @param assetValue - Asset value tot transfer, if assetValue=0 - genesis, value should be retrieved from asset info
         * @param split - split value into this number of outputs
         * @return SendRequest object or null on failure
         */

        public static SendRequest to(Address destination, BigInteger value, int assetID, BigInteger assetValue,
                int split) {

            if (value.longValue() % split != 0) {
                return null;
            }

            if (assetValue.longValue() % split != 0) {
                return null;
            }

            BigInteger bi = value.divide(BigInteger.valueOf(split));
            SendRequest req = to(destination, bi);

            req.assetTransfers = new ArrayList<CSAssetTransfer>();
            req.addAssetTransfer(req.tx.getOutputs().size() - 1, assetID, assetValue, split);

            for (int i = 1; i < split; i++) {
                req.tx.addOutput(bi, destination);
            }

            return req;
        }

        public boolean addInput(Wallet InputWallet, CSTransactionOutput InputTxOut) {
            LinkedList<TransactionOutput> candidates = InputWallet.CS.calculateAllTxOuts();

            for (TransactionOutput output : candidates) {
                if (InputTxOut.getTxID().equals(output.parentTransaction.getHash())) {
                    if (InputTxOut.getIndex() == output.getIndex()) {
                        tx.addInput(output);
                        return true;
                    }
                }
            }

            return false;
        }

        private void resetTxInputs(List<TransactionInput> originalInputs) {
            tx.clearInputs();
            for (TransactionInput input : originalInputs) {
                tx.addInput(input);
            }
        }

        /* CSPK-mike END */

        /**
         * <p>Creates a new SendRequest to the given pubkey for the given value.</p>
         *
         * <p>Be careful to check the output's value is reasonable using
         * {@link TransactionOutput#getMinNonDustValue(BigInteger)} afterwards or you risk having the transaction
         * rejected by the network. Note that using {@link SendRequest#to(Address, java.math.BigInteger)} will result
         * in a smaller output, and thus the ability to use a smaller output value without rejection.</p>
         */
        public static SendRequest to(NetworkParameters params, ECKey destination, BigInteger value) {
            SendRequest req = new SendRequest();
            req.tx = new Transaction(params);
            req.tx.addOutput(value, destination);
            return req;
        }

        /** Simply wraps a pre-built incomplete transaction provided by you. */
        public static SendRequest forTx(Transaction tx) {
            SendRequest req = new SendRequest();
            req.tx = tx;
            return req;
        }

        public static SendRequest emptyWallet(Address destination) {
            SendRequest req = new SendRequest();
            final NetworkParameters parameters = destination.getParameters();
            checkNotNull(parameters, "Address is for an unknown network");
            req.tx = new Transaction(parameters);
            req.tx.addOutput(BigInteger.ZERO, destination);
            req.emptyWallet = true;
            return req;
        }
    }

    /**
     * <p>Statelessly creates a transaction that sends the given value to address. The change is sent to
     * {@link Wallet#getChangeAddress()}, so you must have added at least one key.</p>
     *
     * <p>If you just want to send money quickly, you probably want
     * {@link Wallet#sendCoins(TransactionBroadcaster, Address, java.math.BigInteger)} instead. That will create the sending
     * transaction, commit to the wallet and broadcast it to the network all in one go. This method is lower level
     * and lets you see the proposed transaction before anything is done with it.</p>
     *
     * <p>This is a helper method that is equivalent to using {@link Wallet.SendRequest#to(Address, java.math.BigInteger)}
     * followed by {@link Wallet#completeTx(Wallet.SendRequest)} and returning the requests transaction object.
     * Note that this means a fee may be automatically added if required, if you want more control over the process,
     * just do those two steps yourself.</p>
     *
     * <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 {@link Wallet#commitTx(Transaction)} 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.</p>
     *
     * <p>You MUST ensure that nanocoins is larger than {@link Transaction#MIN_NONDUST_OUTPUT} or the transaction will
     * almost certainly be rejected by the network as dust.</p>
     *
     * @param address       The Bitcoin address to send the money to.
     * @param nanocoins     How much currency to send, in nanocoins.
     * @return either the created Transaction or null if there are insufficient coins.
     * coins as spent until commitTx is called on the result.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public Transaction createSend(Address address, BigInteger nanocoins)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        SendRequest req = SendRequest.to(address, nanocoins);
        completeTx(req);
        return req.tx;
    }

    /**
     * Sends coins to the given address but does not broadcast the resulting pending transaction. It is still stored
     * in the wallet, so when the wallet is added to a {@link PeerGroup} or {@link Peer} the transaction will be
     * announced to the network. The given {@link SendRequest} is completed first using
     * {@link Wallet#completeTx(Wallet.SendRequest)} to make it valid.
     *
     * @return the Transaction that was created
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public Transaction sendCoinsOffline(SendRequest request)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        lock.lock();
        try {
            completeTx(request);
            commitTx(request.tx);
            return request.tx;
        } finally {
            lock.unlock();
        }
    }

    /**
     * <p>Sends coins to the given address, via the given {@link PeerGroup}. Change is returned to
     * {@link Wallet#getChangeAddress()}. Note that a fee may be automatically added if one may be required for the
     * transaction to be confirmed.</p>
     *
     * <p>The returned object provides both the transaction, and a future that can be used to learn when the broadcast
     * is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to
     * the socket. Otherwise when the transaction is written out and we heard it back from a different peer.</p>
     *
     * <p>Note that the sending transaction is committed to the wallet immediately, not when the transaction is
     * successfully broadcast. This means that even if the network hasn't heard about your transaction you won't be
     * able to spend those same coins again.</p>
     *
     * <p>You MUST ensure that value is smaller than {@link Transaction#MIN_NONDUST_OUTPUT} or the transaction will
     * almost certainly be rejected by the network as dust.</p>
     *
     * @param broadcaster a {@link TransactionBroadcaster} to use to send the transactions out.
     * @param to        Which address to send coins to.
     * @param value     How much value to send. You can use Utils.toNanoCoins() to calculate this.
     * @return An object containing the transaction that was created, and a future for the broadcast of it.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public SendResult sendCoins(TransactionBroadcaster broadcaster, Address to, BigInteger value)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        SendRequest request = SendRequest.to(to, value);
        return sendCoins(broadcaster, request);
    }

    /**
     * <p>Sends coins according to the given request, via the given {@link TransactionBroadcaster}.</p>
     *
     * <p>The returned object provides both the transaction, and a future that can be used to learn when the broadcast
     * is complete. Complete means, if the PeerGroup is limited to only one connection, when it was written out to
     * the socket. Otherwise when the transaction is written out and we heard it back from a different peer.</p>
     *
     * <p>Note that the sending transaction is committed to the wallet immediately, not when the transaction is
     * successfully broadcast. This means that even if the network hasn't heard about your transaction you won't be
     * able to spend those same coins again.</p>
     *
     * @param broadcaster the target to use for broadcast.
     * @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
     * @return An object containing the transaction that was created, and a future for the broadcast of it.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public SendResult sendCoins(TransactionBroadcaster broadcaster, SendRequest request)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        // Should not be locked here, as we're going to call into the broadcaster and that might want to hold its
        // own lock. sendCoinsOffline handles everything that needs to be locked.
        checkState(!lock.isHeldByCurrentThread());

        // Commit the TX to the wallet immediately so the spent coins won't be reused.
        // TODO: We should probably allow the request to specify tx commit only after the network has accepted it.
        Transaction tx = sendCoinsOffline(request);
        SendResult result = new SendResult();
        result.tx = tx;
        // The tx has been committed to the pending pool by this point (via sendCoinsOffline -> commitTx), so it has
        // a txConfidenceListener registered. Once the tx is broadcast the peers will update the memory pool with the
        // count of seen peers, the memory pool will update the transaction confidence object, that will invoke the
        // txConfidenceListener which will in turn invoke the wallets event listener onTransactionConfidenceChanged
        // method.
        result.broadcastComplete = broadcaster.broadcastTransaction(tx);
        return result;
    }

    public void completeTx(SendRequest req) throws InsufficientMoneyException, CSExceptions.CannotEncode {
        completeTx(req, true);
    }

    /**
     * Satisfies the given {@link SendRequest} using the default transaction broadcaster configured either via
     * {@link PeerGroup#addWallet(Wallet)} or directly with {@link #setTransactionBroadcaster(TransactionBroadcaster)}.
     *
     * @param request the SendRequest that describes what to do, get one using static methods on SendRequest itself.
     * @return An object containing the transaction that was created, and a future for the broadcast of it.
     * @throws IllegalStateException if no transaction broadcaster has been configured.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public SendResult sendCoins(SendRequest request) throws InsufficientMoneyException, CSExceptions.CannotEncode {
        TransactionBroadcaster broadcaster = vTransactionBroadcaster;
        checkState(broadcaster != null, "No transaction broadcaster is configured");
        return sendCoins(broadcaster, request);
    }

    /**
     * Sends coins to the given address, via the given {@link Peer}. Change is returned to {@link Wallet#getChangeAddress()}.
     * If an exception is thrown by {@link Peer#sendMessage(Message)} the transaction is still committed, so the
     * pending transaction must be broadcast <b>by you</b> at some other time. Note that a fee may be automatically added
     * if one may be required for the transaction to be confirmed.
     *
     * @return The {@link Transaction} that was created or null if there was insufficient balance to send the coins.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     */
    public Transaction sendCoins(Peer peer, SendRequest request)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        Transaction tx = sendCoinsOffline(request);
        peer.sendMessage(tx);
        return tx;
    }

    /**
     * Given a spend request containing an incomplete transaction, makes it valid by adding outputs and signed inputs
     * according to the instructions in the request. The transaction in the request is modified by this method, as is
     * the fee parameter.
     *
     * @param req a SendRequest that contains the incomplete transaction and details for how to make it valid.
     * @throws InsufficientMoneyException if the request could not be completed due to not enough balance.
     * @throws IllegalArgumentException if you try and complete the same SendRequest twice, or if the given send request
     *         cannot be completed without violating the protocol rules.
     */
    public void completeTx(SendRequest req, boolean sign)
            throws InsufficientMoneyException, CSExceptions.CannotEncode {
        lock.lock();
        try {
            checkArgument(!req.completed, "Given SendRequest has already been completed.");
            // Calculate the amount of value we need to import.
            BigInteger value = BigInteger.ZERO;
            for (TransactionOutput output : req.tx.getOutputs()) {
                value = value.add(output.getValue());
            }
            BigInteger totalOutput = value;

            log.info("Completing send tx with {} outputs totalling {} satoshis (not including fees)",
                    req.tx.getOutputs().size(), value);

            // If any inputs have already been added, we don't need to get their value from wallet
            BigInteger totalInput = BigInteger.ZERO;

            /* CSPK-mike START */
            /* Code commented out, input value is calculated after asset inputs were added            
                        for (TransactionInput input : req.tx.getInputs())
            if (input.getConnectedOutput() != null)
                totalInput = totalInput.add(input.getConnectedOutput().getValue());
            else
                log.warn("SendRequest transaction already has inputs but we don't know how much they are worth - they will be added to fee.");
                        value = value.subtract(totalInput);
            */
            /* CSPK-mike START */

            List<TransactionInput> originalInputs = new ArrayList<TransactionInput>(req.tx.getInputs());

            // We need to know if we need to add an additional fee because one of our values are smaller than 0.01 BTC
            boolean needAtLeastReferenceFee = false;
            if (req.ensureMinRequiredFee && !req.emptyWallet) { // min fee checking is handled later for emptyWallet
                for (TransactionOutput output : req.tx.getOutputs())
                    if (output.getValue().compareTo(Utils.CENT) < 0) {
                        if (output.getValue().compareTo(output.getMinNonDustValue()) < 0)
                            throw new IllegalArgumentException(
                                    "Tried to send dust with ensureMinRequiredFee set - no way to complete this");
                        needAtLeastReferenceFee = true;
                        break;
                    }
            }

            // Calculate a list of ALL potential candidates for spending and then ask a coin selector to provide us
            // with the actual outputs that'll be used to gather the required amount of value. In this way, users
            // can customize coin selection policies.
            //
            // Note that this code is poorly optimized: the spend candidates only alter when transactions in the wallet
            // change - it could be pre-calculated and held in RAM, and this is probably an optimization worth doing.
            // Note that output.isMine(this) needs to test the keychain which is currently an array, so it's
            // O(candidate outputs ^ keychain.size())! There's lots of low hanging fruit here.
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            CoinSelection bestCoinSelection;
            TransactionOutput bestChangeOutput = null;
            if (!req.emptyWallet) {
                // This can throw InsufficientMoneyException.
                FeeCalculation feeCalculation;
                /* CSPK-mike START */
                //                feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee, candidates);
                // Fee and inputs calculation

                /*               
                                CoinSparkMessagePart [] MessageParts=new CoinSparkMessagePart[1];
                                MessageParts[0]=new CoinSparkMessagePart();
                                MessageParts[0].mimeType="text/plain";
                                MessageParts[0].fileName=null;
                                MessageParts[0].content="Hello World!".getBytes();
                                String [] DeliveryServers=new String [] {"assets1.coinspark.org/","assets1.coinspark.org/abc"};//,"144.76.175.228/" };
                                req.setMessage(MessageParts, DeliveryServers);
                    
                                CoinSparkPaymentRef paymentRef=new CoinSparkPaymentRef(125);
                                req.setPaymentRef(paymentRef);
                */

                if (CS.createAssetTransfers(req, originalInputs, candidates)) {
                    totalInput = BigInteger.ZERO;
                    for (TransactionInput input : req.tx.getInputs()) {
                        if (input.getConnectedOutput() != null) {
                            totalInput = totalInput.add(input.getConnectedOutput().getValue());
                        } else {
                            log.warn(
                                    "SendRequest transaction already has inputs but we don't know how much they are worth - they will be added to fee.");
                        }
                    }
                    value = totalOutput;
                    value = value.subtract(totalInput);

                    originalInputs = new ArrayList<TransactionInput>(req.tx.getInputs());
                    // Coinspark transaction has to have change output even if there are no explicit transfers.
                    feeCalculation = new FeeCalculation(req, value, originalInputs, needAtLeastReferenceFee,
                            candidates, Transaction.MIN_NONDUST_OUTPUT);
                } else {
                    throw new InsufficientMoneyException.CouldNotAdjustDownwards();
                }

                /* CSPK-mike END */
                bestCoinSelection = feeCalculation.bestCoinSelection;
                bestChangeOutput = feeCalculation.bestChangeOutput;
            } else {
                // We're being asked to empty the wallet. What this means is ensuring "tx" has only a single output
                // of the total value we can currently spend as determined by the selector, and then subtracting the fee.
                checkState(req.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only.");
                CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
                bestCoinSelection = selector.select(NetworkParameters.MAX_MONEY, candidates);
                req.tx.getOutput(0).setValue(bestCoinSelection.valueGathered);

                totalOutput = bestCoinSelection.valueGathered;
            }

            for (TransactionOutput output : bestCoinSelection.gathered)
                req.tx.addInput(output);

            /* CSPK-mike START */
            if (!CS.preparePaymentRef(req)) {
                throw new CSExceptions.CannotEncode("Cannot prepare payment reference");
            }
            if (!CS.prepareMessage(req)) {
                throw new CSExceptions.CannotEncode("Cannot prepare message");
            }
            /* CSPK-mike END */

            if (req.ensureMinRequiredFee && req.emptyWallet) {
                final BigInteger baseFee = req.fee == null ? BigInteger.ZERO : req.fee;
                final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb;
                Transaction tx = req.tx;
                if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb))
                    throw new InsufficientMoneyException.CouldNotAdjustDownwards();
            }

            /* CSPK-mike START */
            // Input calculation for "Empty wallet" request
            if (req.emptyWallet) {
                Transaction tx = req.tx;
                TransactionOutput output = tx.getOutput(0);
                if (CS.createAssetTransfersForEmptyWallet(req,
                        output.getValue().subtract(output.getMinNonDustValue()))) {
                    if (req.ensureMinRequiredFee) {
                        final BigInteger baseFee = req.fee == null ? BigInteger.ZERO : req.fee;
                        final BigInteger feePerKb = req.feePerKb == null ? BigInteger.ZERO : req.feePerKb;
                        totalOutput = bestCoinSelection.valueGathered;
                        output.setValue(totalOutput);
                        if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb, req.assetFee)) {
                            CS.log.warning("Empty wallet: not enough bitcoins to transfer assets.");
                            tx.getOutputs().clear();
                            output.setValue(totalOutput);
                            tx.addOutput(output);
                            req.assetsEncoded = null;
                            req.assetFee = BigInteger.ZERO;
                            if (!adjustOutputDownwardsForFee(tx, bestCoinSelection, baseFee, feePerKb)) {
                                throw new InsufficientMoneyException.CouldNotAdjustDownwards();
                            }
                        }
                        totalOutput = output.getValue();
                        bestChangeOutput = null;
                    }
                }
                totalOutput = output.getValue();
                bestChangeOutput = null;
            }
            /* CSPK-mike END */

            totalInput = totalInput.add(bestCoinSelection.valueGathered);

            if (bestChangeOutput != null) {
                req.tx.addOutput(bestChangeOutput);
                totalOutput = totalOutput.add(bestChangeOutput.getValue());
                log.info("  with {} coins change", bitcoinValueToFriendlyString(bestChangeOutput.getValue()));
            }
            final BigInteger calculatedFee = totalInput.subtract(totalOutput);
            if (calculatedFee.compareTo(BigInteger.ZERO) > 0) {
                log.info("  with a fee of {}", bitcoinValueToFriendlyString(calculatedFee));
            }

            // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.
            if (sign) {
                sign(req);
            }

            // Check size.
            int size = req.tx.bitcoinSerialize().length;
            if (size > Transaction.MAX_STANDARD_TX_SIZE) {
                throw new IllegalArgumentException(
                        String.format("Transaction could not be created without exceeding max size: %d vs %d", size,
                                Transaction.MAX_STANDARD_TX_SIZE));
            }

            // Label the transaction as being self created. We can use this later to spend its change output even before
            // the transaction is confirmed. We deliberately won't bother notifying listeners here as there's not much
            // point - the user isn't interested in a confidence transition they made themselves.
            req.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);

            // Keep a track of the date the tx was created (used in MultiBitService
            // to work out the block it appears in).
            req.tx.setUpdateTime(new Date());

            // Label the transaction as being a user requested payment. This can be used to render GUI wallet
            // transaction lists more appropriately, especially when the wallet starts to generate transactions itself
            // for internal purposes.
            req.tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
            req.completed = true;
            req.fee = calculatedFee;
            log.info("  completed: {}", req.tx);
        } finally {
            lock.unlock();
        }
    }

    public void sign(SendRequest sendRequest) throws CSExceptions.CannotEncode {
        // Now sign the inputs, thus proving that we are entitled to redeem the connected outputs.

        try {
            sendRequest.tx.signInputs(Transaction.SigHash.ALL, this, sendRequest.aesKey);
        } 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);
        }
        /* CSPK-mike START */
        // After inputs are signed we can send messahe ot delivery server
        if (sendRequest.messageToCreate != null) {
            ImmutablePair<Integer, String> payload = new ImmutablePair<Integer, String>(sendRequest.hashCode(),
                    sendRequest.messageToCreate.getServerURL());
            try {
                CSEventBus.INSTANCE.postAsyncEvent(CSEventType.MESSAGE_UPLOAD_STARTED, payload);
                if (sendRequest.messageParts != null) {
                    CS.log.info("Sending message for tx " + sendRequest.tx.getHashAsString()
                            + " to delivery server " + sendRequest.messageToCreate.getServerURL());
                    sendRequest.messageToCreate.setTxID(sendRequest.tx.getHashAsString());

                    // Enable sending from wallets with password
                    sendRequest.messageToCreate.setAesKey(sendRequest.aesKey);

                    try {
                        if (!sendRequest.messageToCreate.create(this, sendRequest.messageParts,
                                sendRequest.createNonce)) {
                            throw new CSExceptions.CannotEncode("Cannot send message to delivery server");
                        }
                    } catch (CSExceptions.CannotEncode e) {
                        // Catch error if create() throws an exception, or result was false
                        CS.log.info("Cannot create message for tx " + sendRequest.tx.getHashAsString()
                                + " on delivery server " + sendRequest.messageToCreate.getServerURL());
                        throw e;
                    }
                }

                if (!CS.messageDB.insertSentMessage(sendRequest.tx.getHashAsString(),
                        sendRequest.tx.getOutputs().size(), sendRequest.messageToCreate.getPaymentRef(),
                        sendRequest.message, sendRequest.messageParts,
                        sendRequest.messageToCreate.getMessageParams())) {
                    CS.log.info("Cannot store message for tx " + sendRequest.tx.getHashAsString());
                    throw new CSExceptions.CannotEncode("Cannot store message in the database");
                }

                if (sendRequest.messageParts != null) {
                    CS.log.info("Message for tx " + sendRequest.tx.getHashAsString()
                            + " was successfully sent via delivery server "
                            + sendRequest.messageToCreate.getServerURL());
                }

            } finally {
                CSEventBus.INSTANCE.postAsyncEvent(CSEventType.MESSAGE_UPLOAD_ENDED, payload);
            }
        }
        /* CSPK-mike END */
    }

    /** Reduce the value of the first output of a transaction to pay the given feePerKb as appropriate for its size. */
    private boolean adjustOutputDownwardsForFee(Transaction tx, CoinSelection coinSelection, BigInteger baseFee,
            BigInteger feePerKb) {
        TransactionOutput output = tx.getOutput(0);
        // Check if we need additional fee due to the transaction's size
        int size = tx.bitcoinSerialize().length;
        size += estimateBytesForSigning(coinSelection);
        BigInteger fee = baseFee.add(BigInteger.valueOf((size / 1000) + 1).multiply(feePerKb));
        output.setValue(output.getValue().subtract(fee));
        // Check if we need additional fee due to the output's value
        if (output.getValue().compareTo(Utils.CENT) < 0
                && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
            output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
        return output.getMinNonDustValue().compareTo(output.getValue()) <= 0;
    }

    /* CSPK-mike START */
    // Version of adjustment function above taking into account minimal fee
    private boolean adjustOutputDownwardsForFee(Transaction tx, CoinSelection coinSelection, BigInteger baseFee,
            BigInteger feePerKb, BigInteger minFee) {
        TransactionOutput output = tx.getOutput(0);
        // Check if we need additional fee due to the transaction's size
        int size = tx.bitcoinSerialize().length;
        size += estimateBytesForSigning(coinSelection);
        BigInteger fee = baseFee.add(BigInteger.valueOf((size / 1000) + 1).multiply(feePerKb));
        if (fee.compareTo(minFee) < 0) {
            fee = minFee;
        }
        output.setValue(output.getValue().subtract(fee));
        // Check if we need additional fee due to the output's value
        if (output.getValue().compareTo(Utils.CENT) < 0
                && fee.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
            output.setValue(output.getValue().subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fee)));
        }
        return output.getMinNonDustValue().compareTo(output.getValue()) <= 0;
    }
    /* CSPK-mike END */

    /**
     * Returns a list of all possible outputs we could possibly spend, potentially even including immature coinbases
     * (which the protocol may forbid us from spending). In other words, return all outputs that this wallet holds
     * keys for and which are not already marked as spent.
     */
    public LinkedList<TransactionOutput> calculateAllSpendCandidates(boolean excludeImmatureCoinbases) {
        lock.lock();
        try {
            LinkedList<TransactionOutput> candidates = Lists.newLinkedList();
            for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
                // Do not try and spend coinbases that were mined too recently, the protocol forbids it.
                if (excludeImmatureCoinbases && !tx.isMature())
                    continue;
                for (TransactionOutput output : tx.getOutputs()) {
                    if (!output.isAvailableForSpending())
                        continue;
                    if (!output.isMine(this))
                        continue;
                    candidates.add(output);
                }
            }
            return candidates;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns all the outputs that match addresses or scripts added via {@link #addWatchedAddress(Address)} or
     * {@link #addWatchedScripts(java.util.List)}.
     * @param excludeImmatureCoinbases Whether to ignore outputs that are unspendable due to being immature.
     */
    public LinkedList<TransactionOutput> getWatchedOutputs(boolean excludeImmatureCoinbases) {
        lock.lock();
        try {
            LinkedList<TransactionOutput> candidates = Lists.newLinkedList();
            for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
                if (excludeImmatureCoinbases && !tx.isMature())
                    continue;
                for (TransactionOutput output : tx.getOutputs()) {
                    if (!output.isAvailableForSpending())
                        continue;
                    try {
                        Script scriptPubKey = output.getScriptPubKey();
                        if (!watchedScripts.contains(scriptPubKey))
                            continue;
                        candidates.add(output);
                    } catch (ScriptException e) {
                        // Ignore
                    }
                }
            }
            return candidates;
        } finally {
            lock.unlock();
        }
    }

    /** Returns the address used for change outputs. Note: this will probably go away in future. */
    public Address getChangeAddress() {
        lock.lock();
        try {
            // For now let's just pick the second key in our keychain. In future we might want to do something else to
            // give the user better privacy here, eg in incognito mode.
            // The second key is chosen rather than the first because, by default, a wallet is created with a 
            // single key. If the user imports say a blockchain.info backup they typically want change to go
            // to one of the imported keys
            checkState(keychain.size() > 0, "Can't send value without an address to use for receiving change");
            ECKey change = keychain.get(0);

            if (keychain.size() > 1) {
                change = keychain.get(1);
            }
            return change.toAddress(params);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Adds the given ECKey to the wallet. There is currently no way to delete keys (that would result in coin loss).
     * If {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.wallet.WalletFiles.Listener)}
     * has been called, triggers an auto save bypassing the normal coalescing delay and event handlers.
     * If the key already exists in the wallet, does nothing and returns false.
     */
    public boolean addKey(final ECKey key) {
        return addKeys(Lists.newArrayList(key)) == 1;
    }

    /**
     * Adds the given keys to the wallet. There is currently no way to delete keys (that would result in coin loss).
     * If {@link Wallet#autosaveToFile(java.io.File, long, java.util.concurrent.TimeUnit, com.google.bitcoin.wallet.WalletFiles.Listener)}
     * has been called, triggers an auto save bypassing the normal coalescing delay and event handlers.
     * Returns the number of keys added, after duplicates are ignored. The onKeyAdded event will be called for each key
     * in the list that was not already present.
     */
    public int addKeys(final List<ECKey> keys) {
        lock.lock();
        try {
            int added = 0;
            // TODO: Consider making keys a sorted list or hashset so membership testing is faster.
            for (final ECKey key : keys) {
                if (keychain.contains(key))
                    continue;

                // If the key has a keyCrypter that does not match the Wallet's then a KeyCrypterException is thrown.
                // This is done because only one keyCrypter is persisted per Wallet and hence all the keys must be homogenous.
                if (isEncrypted() && (!key.isEncrypted() || !keyCrypter.equals(key.getKeyCrypter()))) {
                    throw new KeyCrypterException("Cannot add key " + key.toString()
                            + " because the keyCrypter does not match the wallets. Keys must be homogenous.");
                } else if (key.isEncrypted() && !isEncrypted()) {
                    throw new KeyCrypterException("Cannot add key because it's encrypted and this wallet is not.");
                }
                keychain.add(key);
                added++;
            }
            queueOnKeysAdded(keys);
            // Force an auto-save immediately rather than queueing one, as keys are too important to risk losing.
            //saveNow();
            return added;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Return true if we are watching this address.
     */
    public boolean isAddressWatched(Address address) {
        Script script = ScriptBuilder.createOutputScript(address);
        return isWatchedScript(script);
    }

    /**
     * Same as {@link #addWatchedAddress(Address, long)} with the current time as the creation time.
     */
    public boolean addWatchedAddress(final Address address) {
        long now = Utils.currentTimeMillis() / 1000;
        return addWatchedAddresses(Lists.newArrayList(address), now) == 1;
    }

    /**
     * Adds the given address to the wallet to be watched. Outputs can be retrieved by {@link #getWatchedOutputs(boolean)}.
     *
     * @param creationTime creation time in seconds since the epoch, for scanning the blockchain
     * @return whether the address was added successfully (not already present)
     */
    public boolean addWatchedAddress(final Address address, long creationTime) {
        return addWatchedAddresses(Lists.newArrayList(address), creationTime) == 1;
    }

    /**
     * Adds the given address to the wallet to be watched. Outputs can be retrieved
     * by {@link #getWatchedOutputs(boolean)}.
     *
     * @return how many addresses were added successfully
     */
    public int addWatchedAddresses(final List<Address> addresses, long creationTime) {
        List<Script> scripts = Lists.newArrayList();

        for (Address address : addresses) {
            Script script = ScriptBuilder.createOutputScript(address);
            script.setCreationTimeSeconds(creationTime);
            scripts.add(script);
        }

        return addWatchedScripts(scripts);
    }

    /**
     * Adds the given output scripts to the wallet to be watched. Outputs can be retrieved
     * by {@link #getWatchedOutputs(boolean)}.
     *
     * @return how many scripts were added successfully
     */
    public int addWatchedScripts(final List<Script> scripts) {
        lock.lock();
        try {
            int added = 0;
            for (final Script script : scripts) {
                if (watchedScripts.contains(script))
                    continue;

                watchedScripts.add(script);
                added++;
            }

            queueOnScriptsAdded(scripts);
            saveNow();
            return added;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 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.
     */
    @Nullable
    public ECKey findKeyFromPubHash(byte[] pubkeyHash) {
        lock.lock();
        try {
            for (ECKey key : keychain) {
                if (Arrays.equals(key.getPubKeyHash(), pubkeyHash))
                    return key;
            }
            return null;
        } finally {
            lock.unlock();
        }
    }

    /** Returns true if the given key is in the wallet, false otherwise. Currently an O(N) operation. */
    public boolean hasKey(ECKey key) {
        lock.lock();
        try {
            return keychain.contains(key);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns true if this wallet contains a public key which hashes to the given hash.
     */
    public boolean isPubKeyHashMine(byte[] pubkeyHash) {
        return findKeyFromPubHash(pubkeyHash) != null;
    }

    /** Returns true if this wallet is watching transactions for outputs with the script. */
    public boolean isWatchedScript(Script script) {
        lock.lock();
        try {
            return watchedScripts.contains(script);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Locates a keypair from the keychain given the raw public key bytes.
     * @return ECKey or null if no such key was found.
     */
    @Nullable
    public ECKey findKeyFromPubKey(byte[] pubkey) {
        lock.lock();
        try {
            for (ECKey key : keychain) {
                if (Arrays.equals(key.getPubKey(), pubkey))
                    return key;
            }
            return null;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns true if this wallet contains a keypair with the given public key.
     */
    public boolean isPubKeyMine(byte[] pubkey) {
        return findKeyFromPubKey(pubkey) != null;
    }

    /**
     * <p>It's possible to calculate a wallets balance from multiple points of view. This enum selects which
     * getBalance() should use.</p>
     *
     * <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 includes the value of immature coinbase transactions.
         */
        ESTIMATED,

        /**
         * Balance that can be safely used to create new spends. This is whatever the default coin selector would
         * make available, which by default means transaction outputs with at least 1 confirmation and pending
         * transactions created by our own wallet which have been propagated across the network.
         */
        AVAILABLE
    }

    /**
     * Returns the AVAILABLE balance of this wallet. See {@link BalanceType#AVAILABLE} for details on what this
     * means.
     */
    public BigInteger getBalance() {
        return getBalance(BalanceType.AVAILABLE);
    }

    /**
     * Returns the balance of this wallet as calculated by the provided balanceType.
     */
    public BigInteger getBalance(BalanceType balanceType) {
        lock.lock();
        try {
            if (balanceType == BalanceType.AVAILABLE) {
                return getBalance(coinSelector);
            } else if (balanceType == BalanceType.ESTIMATED) {
                LinkedList<TransactionOutput> all = calculateAllSpendCandidates(false);
                BigInteger value = BigInteger.ZERO;
                for (TransactionOutput out : all)
                    value = value.add(out.getValue());
                return value;
            } else {
                throw new AssertionError("Unknown balance type"); // Unreachable.
            }
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the balance that would be considered spendable by the given coin selector. Just asks it to select
     * as many coins as possible and returns the total.
     */
    public BigInteger getBalance(CoinSelector selector) {
        lock.lock();
        try {
            checkNotNull(selector);
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);
            CoinSelection selection = selector.select(NetworkParameters.MAX_MONEY, candidates);
            return selection.valueGathered;
        } finally {
            lock.unlock();
        }
    }

    /** Returns the available balance, including any unspent balance at watched addresses */
    public BigInteger getWatchedBalance() {
        return getWatchedBalance(coinSelector);
    }

    /**
    * Returns the balance that would be considered spendable by the given coin selector, including
    * any unspent balance at watched addresses.
    */
    public BigInteger getWatchedBalance(CoinSelector selector) {
        lock.lock();
        try {
            checkNotNull(selector);
            LinkedList<TransactionOutput> candidates = getWatchedOutputs(true);
            CoinSelection selection = selector.select(NetworkParameters.MAX_MONEY, candidates);
            return selection.valueGathered;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public String toString() {
        return toString(false, true, true, null);
    }

    private static final Comparator<Transaction> SORT_ORDER_BY_UPDATE_TIME = new Comparator<Transaction>() {

        @Override
        public int compare(final Transaction tx1, final Transaction tx2) {

            final long time1 = tx1.getUpdateTime().getTime();
            final long time2 = tx2.getUpdateTime().getTime();

            return -(Longs.compare(time1, time2));
        }
    };

    private static final Comparator<Transaction> SORT_ORDER_BY_HEIGHT = new Comparator<Transaction>() {

        @Override
        public int compare(final Transaction tx1, final Transaction tx2) {

            final int height1 = tx1.getConfidence().getAppearedAtChainHeight();
            final int height2 = tx2.getConfidence().getAppearedAtChainHeight();

            return -(Ints.compare(height1, height2));
        }
    };

    /**
     * Formats the wallet as a human readable piece of text. Intended for debugging, the format is not meant to be
     * stable or human readable.
     * @param includePrivateKeys Whether raw private key data should be included.
     * @param includeTransactions Whether to print transaction data.
     * @param includeExtensions Whether to print extension data.
     * @param chain If set, will be used to estimate lock times for block timelocked transactions.
     */
    public String toString(boolean includePrivateKeys, boolean includeTransactions, boolean includeExtensions,
            @Nullable AbstractBlockChain chain) {
        lock.lock();
        try {
            StringBuilder builder = new StringBuilder();
            BigInteger estimatedBalance = getBalance(BalanceType.ESTIMATED);
            BigInteger availableBalance = getBalance(BalanceType.AVAILABLE);
            builder.append(String.format("Wallet containing %s BTC (available: %s BTC) in:%n",
                    bitcoinValueToPlainString(estimatedBalance), bitcoinValueToPlainString(availableBalance)));
            builder.append(String.format("  %d pending transactions%n", pending.size()));
            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 dead transactions%n", dead.size()));
            final Date lastBlockSeenTime = getLastBlockSeenTime();
            final String lastBlockSeenTimeStr = lastBlockSeenTime == null ? "time unknown"
                    : lastBlockSeenTime.toString();
            builder.append(String.format("Last seen best block: %d (%s): %s%n", getLastBlockSeenHeight(),
                    lastBlockSeenTimeStr, getLastBlockSeenHash()));
            if (this.keyCrypter != null) {
                builder.append(String.format("Encryption: %s%n", keyCrypter.toString()));
            }
            // Do the keys.
            builder.append("\nKeys:\n");
            for (ECKey key : keychain) {
                final Address address = key.toAddress(params);
                builder.append("  addr:");
                builder.append(address.toString());
                builder.append(" hash160:");
                builder.append(Utils.bytesToHexString(address.getHash160()));
                builder.append(" ");
                builder.append(includePrivateKeys ? key.toStringWithPrivate() : key.toString());
                builder.append("\n");
            }

            if (!watchedScripts.isEmpty()) {
                builder.append("\nWatched scripts:\n");
                for (Script script : watchedScripts) {
                    builder.append("  ");
                    builder.append(script.toString());
                    builder.append("\n");
                }
            }

            if (includeTransactions) {
                // Print the transactions themselves
                if (pending.size() > 0) {
                    builder.append("\n>>> PENDING:\n");
                    toStringHelper(builder, pending, chain, SORT_ORDER_BY_UPDATE_TIME);
                }
                if (unspent.size() > 0) {
                    builder.append("\n>>> UNSPENT:\n");
                    toStringHelper(builder, unspent, chain, SORT_ORDER_BY_HEIGHT);
                }
                if (spent.size() > 0) {
                    builder.append("\n>>> SPENT:\n");
                    toStringHelper(builder, spent, chain, SORT_ORDER_BY_HEIGHT);
                }
                if (dead.size() > 0) {
                    builder.append("\n>>> DEAD:\n");
                    toStringHelper(builder, dead, chain, SORT_ORDER_BY_HEIGHT);
                }
            }
            if (includeExtensions && extensions.size() > 0) {
                builder.append("\n>>> EXTENSIONS:\n");
                for (WalletExtension extension : extensions.values()) {
                    builder.append(extension).append("\n\n");
                }
            }
            return builder.toString();
        } finally {
            lock.unlock();
        }
    }

    private void toStringHelper(StringBuilder builder, Map<Sha256Hash, Transaction> transactionMap,
            @Nullable AbstractBlockChain chain, @Nullable Comparator<Transaction> sortOrder) {
        checkState(lock.isHeldByCurrentThread());

        final Collection<Transaction> txns;
        if (sortOrder != null) {
            txns = new TreeSet<Transaction>(sortOrder);
            txns.addAll(transactionMap.values());
        } else {
            txns = transactionMap.values();
        }

        for (Transaction tx : txns) {
            try {
                builder.append("Sends ");
                builder.append(Utils.bitcoinValueToFriendlyString(tx.getValueSentFromMe(this)));
                builder.append(" and receives ");
                builder.append(Utils.bitcoinValueToFriendlyString(tx.getValueSentToMe(this)));
                builder.append(", total value ");
                builder.append(Utils.bitcoinValueToFriendlyString(tx.getValue(this)));
                builder.append(".\n");
            } catch (ScriptException e) {
                // Ignore and don't print this line.
            }
            builder.append(tx.toString(chain));
        }
    }

    private static class TxOffsetPair implements Comparable<TxOffsetPair> {
        public final Transaction tx;
        public final int offset;

        public TxOffsetPair(Transaction tx, int offset) {
            this.tx = tx;
            this.offset = offset;
        }

        @Override
        public int compareTo(TxOffsetPair o) {
            return Ints.compare(offset, o.offset);
        }
    }

    /**
     * <p>Don't call this directly. It's not intended for API users.</p>
     *
     * <p>Called by the {@link BlockChain} when the best chain (representing total work done) has changed. This can
     * cause the number of confirmations of a transaction to go higher, lower, drop to zero and can even result in
     * a transaction going dead (will never confirm) due to a double spend.</p>
     *
     * <p>The oldBlocks/newBlocks lists are ordered height-wise from top first to bottom last.</p>
     */
    public void reorganize(StoredBlock splitPoint, List<StoredBlock> oldBlocks, List<StoredBlock> newBlocks)
            throws VerificationException {
        lock.lock();
        try {
            // This runs on any peer thread with the block chain locked.
            //
            // The reorganize functionality of the wallet is tested in ChainSplitTest.java
            //
            // receive() has been called on the block that is triggering the re-org before this is called, with type
            // of SIDE_CHAIN.
            //
            // Note that this code assumes blocks are not invalid - if blocks contain duplicated transactions,
            // transactions that double spend etc then we can calculate the incorrect result. This could open up
            // obscure DoS attacks if someone successfully mines a throwaway invalid block and feeds it to us, just
            // to try and corrupt the internal data structures. We should try harder to avoid this but it's tricky
            // because there are so many ways the block can be invalid.

            // Avoid spuriously informing the user of wallet/tx confidence changes whilst we're re-organizing.
            checkState(confidenceChanged.size() == 0);
            checkState(!insideReorg);
            insideReorg = true;
            checkState(onWalletChangedSuppressions == 0);
            onWalletChangedSuppressions++;

            // Map block hash to transactions that appear in it. We ensure that the map values are sorted according
            // to their relative position within those blocks.
            ArrayListMultimap<Sha256Hash, TxOffsetPair> mapBlockTx = ArrayListMultimap.create();
            for (Transaction tx : getTransactions(true)) {
                Map<Sha256Hash, Integer> appearsIn = tx.getAppearsInHashes();
                if (appearsIn == null)
                    continue; // Pending.
                for (Map.Entry<Sha256Hash, Integer> block : appearsIn.entrySet())
                    mapBlockTx.put(block.getKey(), new TxOffsetPair(tx, block.getValue()));
            }
            for (Sha256Hash blockHash : mapBlockTx.keySet())
                Collections.sort(mapBlockTx.get(blockHash));

            List<Sha256Hash> oldBlockHashes = new ArrayList<Sha256Hash>(oldBlocks.size());

            /* CSPK-mike START */
            int MinHeight = -1;
            int MaxHeight = -1;
            /* CSPK-mike END */

            log.info("Old part of chain (top to bottom):");
            for (StoredBlock b : oldBlocks) {
                /* CSPK-mike START */
                if (MinHeight == -1) {
                    MinHeight = b.getHeight();
                    MaxHeight = b.getHeight();
                } else {
                    if (b.getHeight() < MinHeight) {
                        MinHeight = b.getHeight();
                    }
                    if (b.getHeight() > MaxHeight) {
                        MaxHeight = b.getHeight();
                    }
                }
                /* CSPK-mike END */
                log.info("  {}", b.getHeader().getHashAsString());
                oldBlockHashes.add(b.getHeader().getHash());
            }

            /* CSPK-mike START */
            // Asset references for removed blocks should be cleared.
            CS.log.info("Blockchain reorganizing. Removing blocks " + MinHeight + " - " + MaxHeight);
            CS.clearAssetRefs(MinHeight, MaxHeight);
            /* CSPK-mike END */

            log.info("New part of chain (top to bottom):");
            for (StoredBlock b : newBlocks) {
                /* CSPK-mike START */
                CS.log.info("New block " + b.getHeight() + ": " + b.getHeader().getHashAsString());
                /* CSPK-mike END */
                log.info("  {}", b.getHeader().getHashAsString());
            }

            Collections.reverse(newBlocks); // Need bottom-to-top but we get top-to-bottom.

            // For each block in the old chain, disconnect the transactions in reverse order.
            LinkedList<Transaction> oldChainTxns = Lists.newLinkedList();
            for (Sha256Hash blockHash : oldBlockHashes) {
                for (TxOffsetPair pair : mapBlockTx.get(blockHash)) {
                    Transaction tx = pair.tx;
                    final Sha256Hash txHash = tx.getHash();
                    if (tx.isCoinBase()) {
                        // All the transactions that we have in our wallet which spent this coinbase are now invalid
                        // and will never confirm. Hopefully this should never happen - that's the point of the maturity
                        // rule that forbids spending of coinbase transactions for 100 blocks.
                        //
                        // This could be recursive, although of course because we don't have the full transaction
                        // graph we can never reliably kill all transactions we might have that were rooted in
                        // this coinbase tx. Some can just go pending forever, like the Satoshi client. However we
                        // can do our best.
                        //
                        // TODO: Is it better to try and sometimes fail, or not try at all?
                        killCoinbase(tx);
                    } else {
                        for (TransactionOutput output : tx.getOutputs()) {
                            TransactionInput input = output.getSpentBy();
                            if (input != null)
                                input.disconnect();
                        }
                        for (TransactionInput input : tx.getInputs()) {
                            input.disconnect();
                        }
                        oldChainTxns.add(tx);
                        unspent.remove(txHash);
                        spent.remove(txHash);
                        checkState(!pending.containsKey(txHash));
                        checkState(!dead.containsKey(txHash));
                    }
                }
            }

            // Put all the disconnected transactions back into the pending pool and re-connect them.
            for (Transaction tx : oldChainTxns) {
                // Coinbase transactions on the old part of the chain are dead for good and won't come back unless
                // there's another re-org.
                if (tx.isCoinBase())
                    continue;
                log.info("  ->pending {}", tx.getHash());
                tx.getConfidence().setConfidenceType(ConfidenceType.PENDING); // Wipe height/depth/work data.
                confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.TYPE);
                addWalletTransaction(Pool.PENDING, tx);
                updateForSpends(tx, false);
            }

            // Note that dead transactions stay dead. Consider a chain that Finney attacks T1 and replaces it with
            // T2, so we move T1 into the dead pool. If there's now a re-org to a chain that doesn't include T2, it
            // doesn't matter - the miners deleted T1 from their mempool, will resurrect T2 and put that into the
            // mempool and so T1 is still seen as a losing double spend.

            // The old blocks have contributed to the depth and work done for all the transactions in the
            // wallet that are in blocks up to and including the chain split block.
            // The total depth and work done is calculated here and then subtracted from the appropriate transactions.
            int depthToSubtract = oldBlocks.size();
            BigInteger workDoneToSubtract = BigInteger.ZERO;
            for (StoredBlock b : oldBlocks) {
                workDoneToSubtract = workDoneToSubtract.add(b.getHeader().getWork());
            }
            log.info("depthToSubtract = " + depthToSubtract + ", workDoneToSubtract = " + workDoneToSubtract);
            // Remove depthToSubtract and workDoneToSubtract from all transactions in the wallet except for pending.
            subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, spent.values());
            subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, unspent.values());
            subtractDepthAndWorkDone(depthToSubtract, workDoneToSubtract, dead.values());

            // The effective last seen block is now the split point so set the lastSeenBlockHash.
            setLastBlockSeenHash(splitPoint.getHeader().getHash());

            // For each block in the new chain, work forwards calling receive() and notifyNewBestBlock().
            // This will pull them back out of the pending pool, or if the tx didn't appear in the old chain and
            // does appear in the new chain, will treat it as such and possibly kill pending transactions that
            // conflict.
            for (StoredBlock block : newBlocks) {
                log.info("Replaying block {}", block.getHeader().getHashAsString());
                for (TxOffsetPair pair : mapBlockTx.get(block.getHeader().getHash())) {
                    log.info("  tx {}", pair.tx.getHash());
                    try {
                        receive(pair.tx, block, BlockChain.NewBlockType.BEST_CHAIN, pair.offset);
                    } catch (ScriptException e) {
                        throw new RuntimeException(e); // Cannot happen as these blocks were already verified.
                    }
                }
                notifyNewBestBlock(block);
            }
            checkState(isConsistent());
            final BigInteger balance = getBalance();
            log.info("post-reorg balance is {}", Utils.bitcoinValueToFriendlyString(balance));
            // Inform event listeners that a re-org took place.
            queueOnReorganize();
            insideReorg = false;
            onWalletChangedSuppressions--;
            maybeQueueOnWalletChanged();
            checkBalanceFuturesLocked(balance);
            informConfidenceListenersIfNotReorganizing();
            saveLater();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Subtract the supplied depth and work done from the given transactions.
     */
    private void subtractDepthAndWorkDone(int depthToSubtract, BigInteger workDoneToSubtract,
            Collection<Transaction> transactions) {
        for (Transaction tx : transactions) {
            if (tx.getConfidence().getConfidenceType() == ConfidenceType.BUILDING) {
                tx.getConfidence().setDepthInBlocks(tx.getConfidence().getDepthInBlocks() - depthToSubtract);
                tx.getConfidence().setWorkDone(tx.getConfidence().getWorkDone().subtract(workDoneToSubtract));
                confidenceChanged.put(tx, TransactionConfidence.Listener.ChangeReason.DEPTH);
            }
        }
    }

    /**
     * Returns an immutable view of the transactions currently waiting for network confirmations.
     */
    public Collection<Transaction> getPendingTransactions() {
        lock.lock();
        try {
            return Collections.unmodifiableCollection(pending.values());
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the earliest creation time of keys or watched scripts in this wallet, in seconds since the epoch, ie the min
     * of {@link com.google.bitcoin.core.ECKey#getCreationTimeSeconds()}. This can return zero if at least one key does
     * not have that data (was created before key timestamping was implemented). <p>
     *     
     * This method is most often used in conjunction with {@link PeerGroup#setFastCatchupTimeSecs(long)} in order to
     * optimize chain download for new users of wallet apps. Backwards compatibility notice: if you get zero from this
     * method, you can instead use the time of the first release of your software, as it's guaranteed no users will
     * have wallets pre-dating this time. <p>
     * 
     * If there are no keys in the wallet, the current time is returned.
     */
    @Override
    public long getEarliestKeyCreationTime() {
        lock.lock();
        try {
            long earliestTime = Long.MAX_VALUE;
            for (ECKey key : keychain)
                earliestTime = Math.min(key.getCreationTimeSeconds(), earliestTime);
            for (Script script : watchedScripts)
                earliestTime = Math.min(script.getCreationTimeSeconds(), earliestTime);
            if (earliestTime == Long.MAX_VALUE)
                return Utils.currentTimeMillis() / 1000;
            return earliestTime;
        } finally {
            lock.unlock();
        }
    }

    /** Returns the hash of the last seen best-chain block, or null if the wallet is too old to store this data. */
    @Nullable
    public Sha256Hash getLastBlockSeenHash() {
        lock.lock();
        try {
            return lastBlockSeenHash;
        } finally {
            lock.unlock();
        }
    }

    public void setLastBlockSeenHash(@Nullable Sha256Hash lastBlockSeenHash) {
        lock.lock();
        try {
            this.lastBlockSeenHash = lastBlockSeenHash;
        } finally {
            lock.unlock();
        }
    }

    public void setLastBlockSeenHeight(int lastBlockSeenHeight) {
        lock.lock();
        try {
            this.lastBlockSeenHeight = lastBlockSeenHeight;
        } finally {
            lock.unlock();
        }
    }

    public void setLastBlockSeenTimeSecs(long timeSecs) {
        lock.lock();
        try {
            lastBlockSeenTimeSecs = timeSecs;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns the UNIX time in seconds since the epoch extracted from the last best seen block header. This timestamp
     * is <b>not</b> the local time at which the block was first observed by this application but rather what the block
     * (i.e. miner) self declares. It is allowed to have some significant drift from the real time at which the block
     * was found, although most miners do use accurate times. If this wallet is old and does not have a recorded
     * time then this method returns zero.
     */
    public long getLastBlockSeenTimeSecs() {
        lock.lock();
        try {
            return lastBlockSeenTimeSecs;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Returns a {@link Date} representing the time extracted from the last best seen block header. This timestamp
     * is <b>not</b> the local time at which the block was first observed by this application but rather what the block
     * (i.e. miner) self declares. It is allowed to have some significant drift from the real time at which the block
     * was found, although most miners do use accurate times. If this wallet is old and does not have a recorded
     * time then this method returns null.
     */
    @Nullable
    public Date getLastBlockSeenTime() {
        final long secs = getLastBlockSeenTimeSecs();
        if (secs == 0)
            return null;
        else
            return new Date(secs * 1000);
    }

    /**
     * Returns the height of the last seen best-chain block. Can be 0 if a wallet is brand new or -1 if the wallet
     * is old and doesn't have that data.
     */
    public int getLastBlockSeenHeight() {
        lock.lock();
        try {
            return lastBlockSeenHeight;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Deletes transactions which appeared after a certain date
     */
    public synchronized void clearTransactions(Date fromDate) {
        lock.lock();
        try {
            if (fromDate == null) {
                unspent.clear();
                spent.clear();
                pending.clear();
                dead.clear();
            } else {
                removeEntriesAfterDate(unspent, fromDate);
                removeEntriesAfterDate(spent, fromDate);
                removeEntriesAfterDate(pending, fromDate);
                removeEntriesAfterDate(dead, fromDate);
            }
        } finally {
            lock.unlock();
        }
    }

    private void removeEntriesAfterDate(Map<Sha256Hash, Transaction> pool, Date fromDate) {
        checkState(lock.isLocked());
        log.debug("Wallet#removeEntriesAfterDate - Removing transactions later than " + fromDate.toString());
        Set<Entry<Sha256Hash, Transaction>> loopEntries = pool.entrySet();
        Iterator<Entry<Sha256Hash, Transaction>> iterator = loopEntries.iterator();
        while (iterator.hasNext()) {
            Entry<Sha256Hash, Transaction> member = iterator.next();
            if (member.getValue() != null) {
                Date updateTime = member.getValue().getUpdateTime();
                if (updateTime != null && updateTime.after(fromDate)) {
                    iterator.remove();
                    //log.debug("Wallet#removeEntriesAfterDate - Removed tx.1 " + member.getValue());
                    continue;
                }

                // if no updateTime remove them
                if (updateTime == null || updateTime.getTime() == 0) {
                    iterator.remove();
                    //log.debug("Removed tx.2 " + member.getValue());
                    continue;
                }
            }
        }
    }

    /**
     * Convenience wrapper around {@link Wallet#encrypt(com.google.bitcoin.crypto.KeyCrypter,
     * org.spongycastle.crypto.params.KeyParameter)} which uses the default Scrypt key derivation algorithm and
     * parameters, derives a key from the given password and returns the created key.
     */
    public KeyParameter encrypt(CharSequence password) {
        checkNotNull(password);
        checkArgument(password.length() > 0);
        KeyCrypter scrypt = new KeyCrypterScrypt();
        KeyParameter derivedKey = scrypt.deriveKey(password);
        encrypt(scrypt, derivedKey);
        return derivedKey;
    }

    /**
     * Encrypt the wallet using the KeyCrypter and the AES key. A good default KeyCrypter to use is
     * {@link com.google.bitcoin.crypto.KeyCrypterScrypt}.
     *
     * @param keyCrypter The KeyCrypter that specifies how to encrypt/ decrypt a key
     * @param aesKey AES key to use (normally created using KeyCrypter#deriveKey and cached as it is time consuming to create from a password)
     * @throws KeyCrypterException Thrown if the wallet encryption fails. If so, the wallet state is unchanged.
     */
    public void encrypt(KeyCrypter keyCrypter, KeyParameter aesKey) {
        lock.lock();
        try {
            checkNotNull(keyCrypter);
            checkState(getEncryptionType() == EncryptionType.UNENCRYPTED, "Wallet is already encrypted");
            // Create a new arraylist that will contain the encrypted keys
            ArrayList<ECKey> encryptedKeyChain = new ArrayList<ECKey>();
            for (ECKey key : keychain) {
                if (key.isEncrypted()) {
                    // Key is already encrypted - add as is.
                    encryptedKeyChain.add(key);
                } else {
                    // Encrypt the key.
                    ECKey encryptedKey = key.encrypt(keyCrypter, aesKey);

                    // Check that the encrypted key can be successfully decrypted.
                    // This is done as it is a critical failure if the private key cannot be decrypted successfully
                    // (all bitcoin controlled by that private key is lost forever).
                    // For a correctly constructed keyCrypter the encryption should always be reversible so it is just being as cautious as possible.
                    if (!ECKey.encryptionIsReversible(key, encryptedKey, keyCrypter, aesKey)) {
                        // Abort encryption
                        throw new KeyCrypterException("The key " + key.toString()
                                + " cannot be successfully decrypted after encryption so aborting wallet encryption.");
                    }

                    encryptedKeyChain.add(encryptedKey);
                }
            }

            // Now ready to use the encrypted keychain so go through the old keychain clearing all the unencrypted private keys.
            // (This is to avoid the possibility of key recovery from memory).
            for (ECKey key : keychain) {
                if (!key.isEncrypted()) {
                    key.clearPrivateKey();
                }
            }

            // Replace the old keychain with the encrypted one.
            keychain = encryptedKeyChain;

            // The wallet is now encrypted.
            this.keyCrypter = keyCrypter;

            // Add a MultiBitWalletExtension to protect the wallet from being loaded by earlier MultiBits.
            MultiBitWalletExtension multibitWalletExtension = new MultiBitWalletExtension();
            extensions.put(multibitWalletExtension.getWalletExtensionID(), multibitWalletExtension);

            //saveNow();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Decrypt the wallet with the wallets keyCrypter and AES key.
     *
     * @param aesKey AES key to use (normally created using KeyCrypter#deriveKey and cached as it is time consuming to create from a password)
     * @throws KeyCrypterException Thrown if the wallet decryption fails. If so, the wallet state is unchanged.
     */
    public void decrypt(KeyParameter aesKey) {
        lock.lock();
        try {
            // Check the wallet is already encrypted - you cannot decrypt an unencrypted wallet.
            checkState(getEncryptionType() != EncryptionType.UNENCRYPTED, "Wallet is already decrypted");
            // Check that the wallet keyCrypter is non-null.
            // This is set either at construction (if an encrypted wallet is created) or by wallet encryption.
            checkNotNull(keyCrypter);

            // Create a new arraylist that will contain the decrypted keys
            ArrayList<ECKey> decryptedKeyChain = new ArrayList<ECKey>();

            for (ECKey key : keychain) {
                // Decrypt the key.
                if (!key.isEncrypted()) {
                    // Not encrypted - add to chain as is.
                    decryptedKeyChain.add(key);
                } else {
                    ECKey decryptedECKey = key.decrypt(keyCrypter, aesKey);
                    decryptedKeyChain.add(decryptedECKey);
                }
            }

            // Replace the old keychain with the unencrypted one.
            keychain = decryptedKeyChain;

            // The wallet is now unencrypted.
            keyCrypter = null;

            // Clear the MultBit wallet extension so that earlier MultiBits can load it.
            extensions.remove(MultiBitWalletProtobufSerializer.ORG_MULTIBIT_WALLET_PROTECT_2);

            //saveNow();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Create a new, random encrypted ECKey and add it to the wallet.
     *
     * @param keyCrypter The keyCrypter to use in encrypting the new key
     * @param aesKey The AES key to use to encrypt the new key
     * @return ECKey the new, encrypted ECKey
     */
    public ECKey addNewEncryptedKey(KeyCrypter keyCrypter, KeyParameter aesKey) {
        ECKey newKey = (new ECKey()).encrypt(checkNotNull(keyCrypter), checkNotNull(aesKey));
        addKey(newKey);
        return newKey;
    }

    /**
     * <p>Convenience wrapper around {@link Wallet#addNewEncryptedKey(com.google.bitcoin.crypto.KeyCrypter,
     * org.spongycastle.crypto.params.KeyParameter)} which just derives the key afresh and uses the pre-set
     * keycrypter. The wallet must have been encrypted using one of the encrypt methods previously.</p>
     *
     * <p>Note that key derivation is deliberately very slow! So if you plan to add multiple keys, it can be
     * faster to use the other method instead and re-use the {@link KeyParameter} object instead.</p>
     */
    public ECKey addNewEncryptedKey(CharSequence password) {
        lock.lock();
        try {
            checkNotNull(keyCrypter, "Wallet is not encrypted, you must call encrypt() first.");
            return addNewEncryptedKey(keyCrypter, keyCrypter.deriveKey(password));
        } finally {
            lock.unlock();
        }
    }

    /**
     *  Check whether the password can decrypt the first key in the wallet.
     *  This can be used to check the validity of an entered password.
     *
     *  @return boolean true if password supplied can decrypt the first private key in the wallet, false otherwise.
     */
    public boolean checkPassword(CharSequence password) {
        lock.lock();
        try {
            return keyCrypter != null && checkAESKey(keyCrypter.deriveKey(checkNotNull(password)));
        } finally {
            lock.unlock();
        }
    }

    /**
     *  Check whether the AES key can decrypt the first encrypted key in the wallet.
     *
     *  @return boolean true if AES key supplied can decrypt the first encrypted private key in the wallet, false otherwise.
     */
    public boolean checkAESKey(KeyParameter aesKey) {
        lock.lock();
        try {
            // If no keys then cannot decrypt.
            if (!getKeys().iterator().hasNext())
                return false;
            // Find the first encrypted key in the wallet.
            ECKey firstEncryptedECKey = null;
            Iterator<ECKey> iterator = getKeys().iterator();
            while (iterator.hasNext() && firstEncryptedECKey == null) {
                ECKey loopECKey = iterator.next();
                if (loopECKey.isEncrypted()) {
                    firstEncryptedECKey = loopECKey;
                }
            }
            // There are no encrypted keys in the wallet.
            if (firstEncryptedECKey == null)
                return false;
            String originalAddress = firstEncryptedECKey.toAddress(getNetworkParameters()).toString();
            if (firstEncryptedECKey.isEncrypted() && firstEncryptedECKey.getEncryptedPrivateKey() != null) {
                try {
                    ECKey rebornKey = firstEncryptedECKey.decrypt(keyCrypter, aesKey);

                    // Check that the decrypted private key's address is correct ie it decrypted accurately.
                    String rebornAddress = rebornKey.toAddress(getNetworkParameters()).toString();
                    return originalAddress.equals(rebornAddress);
                } catch (KeyCrypterException ede) {
                    // The AES key supplied is incorrect.
                    return false;
                }
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Get the wallet's KeyCrypter.
     * (Used in encrypting/ decrypting an ECKey).
     */
    public KeyCrypter getKeyCrypter() {
        lock.lock();
        try {
            return keyCrypter;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Sets the wallet's KeyCrypter.
     * Note that this does not encrypt the wallet, and should only be used if the keyCrypter can not be included in the
     * constructor during initial wallet loading.
     * Note that if the keyCrypter was not properly set during wallet load, {@link Wallet#getEncryptionType()} and
     * {@link Wallet#isEncrypted()} will not return the correct results.
     */
    public void setKeyCrypter(KeyCrypter keyCrypter) {
        lock.lock();
        try {
            checkState(this.keyCrypter == null);
            this.keyCrypter = keyCrypter;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Get the type of encryption used for this wallet.
     *
     * (This is a convenience method - the encryption type is actually stored in the keyCrypter).
     */
    public EncryptionType getEncryptionType() {
        lock.lock();
        try {
            if (keyCrypter == null) {
                // Unencrypted wallet.
                return EncryptionType.UNENCRYPTED;
            } else {
                return keyCrypter.getUnderstoodEncryptionType();
            }
        } finally {
            lock.unlock();
        }
    }

    /** Returns true if the wallet is encrypted using any scheme, false if not. */
    public boolean isEncrypted() {
        return getEncryptionType() != EncryptionType.UNENCRYPTED;
    }

    public MultiBitWalletVersion getVersion() {
        return version;
    }

    public void setVersion(MultiBitWalletVersion version) {
        this.version = version;
    }

    /**
     * Set the description of the wallet.
     * This is a Unicode encoding string typically entered by the user as descriptive text for the wallet.
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Get the description of the wallet. See {@link Wallet#setDescription(String))}
     */
    public String getDescription() {
        return description;
    }

    @Override
    public int getBloomFilterElementCount() {
        int size = getKeychainSize() * 2;
        for (Transaction tx : getTransactions(false)) {
            for (TransactionOutput out : tx.getOutputs()) {
                try {
                    if (isTxOutputBloomFilterable(out))
                        size++;
                } catch (ScriptException e) {
                    throw new RuntimeException(e); // If it is ours, we parsed the script correctly, so this shouldn't happen
                }
            }
        }

        // Some scripts may have more than one bloom element.  That should normally be okay,
        // because under-counting just increases false-positive rate.
        size += watchedScripts.size();

        return size;
    }

    /**
     * If we are watching any scripts, the bloom filter must update on peers whenever an output is
     * identified.  This is because we don't necessarily have the associated pubkey, so we can't
     * watch for it on spending transactions.
     */
    @Override
    public boolean isRequiringUpdateAllBloomFilter() {
        return !watchedScripts.isEmpty();
    }

    /**
     * Gets a bloom filter that contains all of the public keys from this wallet, and which will provide the given
     * false-positive rate. See the docs for {@link BloomFilter} for a brief explanation of anonymity when using filters.
     */
    public BloomFilter getBloomFilter(double falsePositiveRate) {
        return getBloomFilter(getBloomFilterElementCount(), falsePositiveRate,
                (long) (Math.random() * Long.MAX_VALUE));
    }

    /**
     * Gets a bloom filter that contains all of the public keys from this wallet,
     * and which will provide the given false-positive rate if it has size elements.
     * Keep in mind that you will get 2 elements in the bloom filter for each key in the wallet.
     * 
     * This is used to generate a BloomFilter which can be #{link BloomFilter.merge}d with another.
     * It could also be used if you have a specific target for the filter's size.
     * 
     * See the docs for {@link BloomFilter(int, double)} for a brief explanation of anonymity when using bloom filters.
     */
    @Override
    public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
        BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
        lock.lock();
        try {
            for (ECKey key : keychain) {
                filter.insert(key.getPubKey());
                filter.insert(key.getPubKeyHash());
            }

            for (Script script : watchedScripts) {
                for (ScriptChunk chunk : script.getChunks()) {
                    // Only add long (at least 64 bit) data to the bloom filter.
                    // If any long constants become popular in scripts, we will need logic
                    // here to exclude them.
                    if (!chunk.isOpCode() && chunk.data.length >= MINIMUM_BLOOM_DATA_LENGTH) {
                        filter.insert(chunk.data);
                    }
                }
            }
        } finally {
            lock.unlock();
        }
        for (Transaction tx : getTransactions(false)) {
            for (int i = 0; i < tx.getOutputs().size(); i++) {
                TransactionOutput out = tx.getOutputs().get(i);
                try {
                    if (isTxOutputBloomFilterable(out)) {
                        TransactionOutPoint outPoint = new TransactionOutPoint(params, i, tx);
                        filter.insert(outPoint.bitcoinSerialize());
                    }
                } catch (ScriptException e) {
                    throw new RuntimeException(e); // If it is ours, we parsed the script correctly, so this shouldn't happen
                }
            }
        }

        return filter;
    }

    private boolean isTxOutputBloomFilterable(TransactionOutput out) {
        return (out.isMine(this) && out.getScriptPubKey().isSentToRawPubKey()) || out.isWatched(this);
    }

    /** Returns the {@link CoinSelector} object which controls which outputs can be spent by this wallet. */
    public CoinSelector getCoinSelector() {
        lock.lock();
        try {
            return coinSelector;
        } finally {
            lock.unlock();
        }
    }

    /**
     * A coin selector is responsible for choosing which outputs to spend when creating transactions. The default
     * selector implements a policy of spending transactions that appeared in the best chain and pending transactions
     * that were created by this wallet, but not others. You can override the coin selector for any given send
     * operation by changing {@link Wallet.SendRequest#coinSelector}.
     */
    public void setCoinSelector(@Nonnull CoinSelector coinSelector) {
        lock.lock();
        try {
            this.coinSelector = checkNotNull(coinSelector);
        } finally {
            lock.unlock();
        }
    }

    /**
     * Convenience wrapper for <tt>setCoinSelector(Wallet.AllowUnconfirmedCoinSelector.get())</tt>. If this method
     * is called on the wallet then transactions will be used for spending regardless of their confidence. This can
     * be dangerous - only use this if you absolutely know what you're doing!
     */
    public void allowSpendingUnconfirmedTransactions() {
        setCoinSelector(AllowUnconfirmedCoinSelector.get());
    }

    private static class BalanceFutureRequest {
        public SettableFuture<BigInteger> future;
        public BigInteger value;
        public BalanceType type;
    }

    @GuardedBy("lock")
    private List<BalanceFutureRequest> balanceFutureRequests = Lists.newLinkedList();

    /**
     * <p>Returns a future that will complete when the balance of the given type has becom equal or larger to the given
     * value. If the wallet already has a large enough balance the future is returned in a pre-completed state. Note
     * that this method is not blocking, if you want to actually wait immediately, you have to call .get() on
     * the result.</p>
     *
     * <p>Also note that by the time the future completes, the wallet may have changed yet again if something else
     * is going on in parallel, so you should treat the returned balance as advisory and be prepared for sending
     * money to fail! Finally please be aware that any listeners on the future will run either on the calling thread
     * if it completes immediately, or eventually on a background thread if the balance is not yet at the right
     * level. If you do something that means you know the balance should be sufficient to trigger the future,
     * you can use {@link com.google.bitcoin.utils.Threading#waitForUserCode()} to block until the future had a
     * chance to be updated.</p>
     */
    public ListenableFuture<BigInteger> getBalanceFuture(final BigInteger value, final BalanceType type) {
        lock.lock();
        try {
            final SettableFuture<BigInteger> future = SettableFuture.create();
            final BigInteger current = getBalance(type);
            if (current.compareTo(value) >= 0) {
                // Already have enough.
                future.set(current);
            } else {
                // Will be checked later in checkBalanceFutures. We don't just add an event listener for ourselves
                // here so that running getBalanceFuture().get() in the user code thread works - generally we must
                // avoid giving the user back futures that require the user code thread to be free.
                BalanceFutureRequest req = new BalanceFutureRequest();
                req.future = future;
                req.value = value;
                req.type = type;
                balanceFutureRequests.add(req);
            }
            return future;
        } finally {
            lock.unlock();
        }
    }

    // Runs any balance futures in the user code thread.
    private void checkBalanceFuturesLocked(@Nullable BigInteger avail) {
        checkState(lock.isHeldByCurrentThread());
        BigInteger estimated = null;
        final ListIterator<BalanceFutureRequest> it = balanceFutureRequests.listIterator();
        while (it.hasNext()) {
            final BalanceFutureRequest req = it.next();
            BigInteger val = null;
            if (req.type == BalanceType.AVAILABLE) {
                if (avail == null)
                    avail = getBalance(BalanceType.AVAILABLE);
                if (avail.compareTo(req.value) < 0)
                    continue;
                val = avail;
            } else if (req.type == BalanceType.ESTIMATED) {
                if (estimated == null)
                    estimated = getBalance(BalanceType.ESTIMATED);
                if (estimated.compareTo(req.value) < 0)
                    continue;
                val = estimated;
            }
            // Found one that's finished.
            it.remove();
            final BigInteger v = checkNotNull(val);
            // Don't run any user-provided future listeners with our lock held.
            Threading.USER_THREAD.execute(new Runnable() {
                @Override
                public void run() {
                    req.future.set(v);
                }
            });
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Extensions to the wallet format.

    /**
     * By providing an object implementing the {@link WalletExtension} interface, you can save and load arbitrary
     * additional data that will be stored with the wallet. Each extension is identified by an ID, so attempting to
     * add the same extension twice (or two different objects that use the same ID) will throw an IllegalStateException.
     */
    public void addExtension(WalletExtension extension) {
        String id = checkNotNull(extension).getWalletExtensionID();
        lock.lock();
        try {
            if (extensions.containsKey(id))
                throw new IllegalStateException("Cannot add two extensions with the same ID: " + id);
            extensions.put(id, extension);
            //saveNow();
        } finally {
            lock.unlock();
        }
    }

    /**
     * Atomically adds extension or returns an existing extension if there is one with the same id alreadypresent.
     */
    public WalletExtension addOrGetExistingExtension(WalletExtension extension) {
        String id = checkNotNull(extension).getWalletExtensionID();
        lock.lock();
        try {
            WalletExtension previousExtension = extensions.get(id);
            if (previousExtension != null)
                return previousExtension;
            extensions.put(id, extension);
            //saveNow();
            return extension;
        } finally {
            lock.unlock();
        }
    }

    /**
     * Either adds extension as a new extension or replaces the existing extension if one already exists with the same
     * id. This also triggers wallet auto-saving, so may be useful even when called with the same extension as is
     * already present.
     */
    public void addOrUpdateExtension(WalletExtension extension) {
        String id = checkNotNull(extension).getWalletExtensionID();
        lock.lock();
        try {
            extensions.put(id, extension);
            //saveNow();
        } finally {
            lock.unlock();
        }
    }

    /** Returns a snapshot of all registered extension objects. The extensions themselves are not copied. */
    public Map<String, WalletExtension> getExtensions() {
        lock.lock();
        try {
            return ImmutableMap.copyOf(extensions);
        } finally {
            lock.unlock();
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Boilerplate for running event listeners - dispatches events onto the user code thread (where we don't do
    // anything and hold no locks).

    private void queueOnTransactionConfidenceChanged(final Transaction tx) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            if (registration.executor == Threading.SAME_THREAD) {
                registration.listener.onTransactionConfidenceChanged(this, tx);
            } else {
                registration.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        registration.listener.onTransactionConfidenceChanged(Wallet.this, tx);
                    }
                });
            }
        }
    }

    private void maybeQueueOnWalletChanged() {
        // Don't invoke the callback in some circumstances, eg, whilst we are re-organizing or fiddling with
        // transactions due to a new block arriving. It will be called later instead.
        checkState(lock.isHeldByCurrentThread());
        checkState(onWalletChangedSuppressions >= 0);
        if (onWalletChangedSuppressions > 0)
            return;
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onWalletChanged(Wallet.this);
                }
            });
        }
    }

    private void queueOnCoinsReceived(final Transaction tx, final BigInteger balance, final BigInteger newBalance) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onCoinsReceived(Wallet.this, tx, balance, newBalance);
                }
            });
        }
    }

    private void queueOnCoinsSent(final Transaction tx, final BigInteger prevBalance, final BigInteger newBalance) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onCoinsSent(Wallet.this, tx, prevBalance, newBalance);
                }
            });
        }
    }

    private void queueOnReorganize() {
        checkState(lock.isHeldByCurrentThread());
        checkState(insideReorg);
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onReorganize(Wallet.this);
                }
            });
        }
    }

    private void queueOnKeysAdded(final List<ECKey> keys) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onKeysAdded(Wallet.this, keys);
                }
            });
        }
    }

    public TransactionConfidence.Listener getTxConfidenceListener() {
        return txConfidenceListener;
    }

    private void queueOnScriptsAdded(final List<Script> scripts) {
        checkState(lock.isHeldByCurrentThread());
        for (final ListenerRegistration<WalletEventListener> registration : eventListeners) {
            registration.executor.execute(new Runnable() {
                @Override
                public void run() {
                    registration.listener.onScriptsAdded(Wallet.this, scripts);
                }
            });
        }
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Fee calculation code.

    private class FeeCalculation {

        private CoinSelection bestCoinSelection;
        private TransactionOutput bestChangeOutput;

        /**
         * This constructor creates selection with output is valueChangeMinimal>Transaction.MIN_NONDUST_OUTPUT.
         * Change is mandatory in CoinSpark transaction as assets may flow implicitly to it.
         * @param req
         * @param value
         * @param originalInputs
         * @param needAtLeastReferenceFee
         * @param candidates
         * @param valueChangeMinimal
         * @throws InsufficientMoneyException 
         */

        public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs,
                boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates,
                BigInteger valueChangeMinimal) throws InsufficientMoneyException {
            checkState(lock.isHeldByCurrentThread());
            // There are 3 possibilities for what adding change might do:
            // 1) No effect
            // 2) Causes increase in fee (change < 0.01 COINS)
            // 3) Causes the transaction to have a dust output or change < fee increase (ie change will be thrown away)
            // If we get either of the last 2, we keep note of what the inputs looked like at the time and try to
            // add inputs as we go up the list (keeping track of minimum inputs for each category).  At the end, we pick
            // the best input set as the one which generates the lowest total fee.
            BigInteger additionalValueForNextCategory = null;
            CoinSelection selection3 = null;
            CoinSelection selection2 = null;
            TransactionOutput selection2Change = null;
            CoinSelection selection1 = null;
            TransactionOutput selection1Change = null;
            // We keep track of the last size of the transaction we calculated but only if the act of adding inputs and
            // change resulted in the size crossing a 1000 byte boundary. Otherwise it stays at zero.
            int lastCalculatedSize = 0;
            BigInteger valueNeeded, valueMissing = null;

            while (true) {
                resetTxInputs(req, originalInputs);

                BigInteger fees = req.fee == null ? BigInteger.ZERO : req.fee;
                if (lastCalculatedSize > 0) {
                    // If the size is exactly 1000 bytes then we'll over-pay, but this should be rare.
                    fees = fees.add(BigInteger.valueOf((lastCalculatedSize / 1000) + 1).multiply(req.feePerKb));
                } else {
                    fees = fees.add(req.feePerKb); // First time around the loop.
                }

                if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
                    fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;

                if (req.ensureMinRequiredFee && fees.compareTo(req.assetFee) < 0) {
                    fees = req.assetFee;
                }

                valueNeeded = value.add(fees);

                if (additionalValueForNextCategory == null)
                    additionalValueForNextCategory = BigInteger.ZERO;

                additionalValueForNextCategory = additionalValueForNextCategory.add(valueChangeMinimal);

                valueNeeded = valueNeeded.add(additionalValueForNextCategory);

                BigInteger additionalValueSelected = additionalValueForNextCategory;

                // Of the coins we could spend, pick some that we actually will spend.
                CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
                CoinSelection selection = selector.select(valueNeeded, candidates);
                // Can we afford this?
                if (selection.valueGathered.compareTo(valueNeeded) < 0) {
                    valueMissing = valueNeeded.subtract(selection.valueGathered);
                    break;
                }
                checkState(selection.gathered.size() > 0 || originalInputs.size() > 0);

                // We keep track of an upper bound on transaction size to calculate fees that need to be added.
                // Note that the difference between the upper bound and lower bound is usually small enough that it
                // will be very rare that we pay a fee we do not need to.
                //
                // We can't be sure a selection is valid until we check fee per kb at the end, so we just store
                // them here temporarily.
                boolean eitherCategory2Or3 = false;
                boolean isCategory3 = false;

                BigInteger change = selection.valueGathered.subtract(valueNeeded);

                change = change.add(additionalValueSelected);

                // If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network
                if (req.ensureMinRequiredFee && !change.equals(BigInteger.ZERO) && change.compareTo(Utils.CENT) < 0
                        && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
                    // This solution may fit into category 2, but it may also be category 3, we'll check that later
                    eitherCategory2Or3 = true;
                    additionalValueForNextCategory = Utils.CENT;
                    // If the change is smaller than the fee we want to add, this will be negative
                    change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees));
                }

                int size = 0;
                TransactionOutput changeOutput = null;
                if (change.compareTo(valueChangeMinimal) >= 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. The address comes either from the request or getChangeAddress() as a default.
                    Address changeAddress = req.changeAddress;
                    if (changeAddress == null)
                        changeAddress = getChangeAddress();
                    changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
                    // If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee
                    if (req.ensureMinRequiredFee && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) {
                        // This solution definitely fits in category 3
                        if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueChangeMinimal) > 0) {
                            isCategory3 = true;
                            additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE
                                    .add(Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE));
                        }
                    } else {
                        size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size())
                                - VarInt.sizeOf(req.tx.getOutputs().size() - 1);
                        // This solution is either category 1 or 2
                        if (!eitherCategory2Or3) // must be category 1
                            additionalValueForNextCategory = null;
                    }
                } else {
                    if (eitherCategory2Or3) {
                        // This solution definitely fits in category 3 (we threw away change because it was smaller than MIN_TX_FEE)
                        // Category 3 is possible only if valueChangeMinimal is a dust
                        if (Transaction.MIN_NONDUST_OUTPUT.compareTo(valueChangeMinimal) > 0) {
                            isCategory3 = true;
                            additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE
                                    .add(BigInteger.ONE);
                        }
                    }
                }

                // Now add unsigned inputs for the selected coins.
                for (TransactionOutput output : selection.gathered) {
                    TransactionInput input = req.tx.addInput(output);
                    // If the scriptBytes don't default to none, our size calculations will be thrown off.
                    checkState(input.getScriptBytes().length == 0);
                }

                // Estimate transaction size and loop again if we need more fee per kb. The serialized tx doesn't
                // include things we haven't added yet like input signatures/scripts or the change output.
                size += req.tx.bitcoinSerialize().length;
                size += estimateBytesForSigning(selection);
                if (size / 1000 > lastCalculatedSize / 1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
                    lastCalculatedSize = size;
                    // We need more fees anyway, just try again with the same additional value
                    additionalValueForNextCategory = additionalValueSelected;
                    continue;
                }

                if (isCategory3) {
                    if (selection3 == null)
                        selection3 = selection;
                } else if (eitherCategory2Or3) {
                    // If we are in selection2, we will require at least CENT additional. If we do that, there is no way
                    // we can end up back here because CENT additional will always get us to 1
                    checkState(selection2 == null);
                    checkState(additionalValueForNextCategory.equals(Utils.CENT));
                    selection2 = selection;
                    selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3
                } else {
                    // Once we get a category 1 (change kept), we should break out of the loop because we can't do better
                    checkState(selection1 == null);
                    checkState(additionalValueForNextCategory == null);
                    selection1 = selection;
                    selection1Change = changeOutput;
                }

                if (additionalValueForNextCategory != null) {
                    checkState(additionalValueForNextCategory.compareTo(additionalValueSelected) > 0);
                    continue;
                }

                break;
            }

            resetTxInputs(req, originalInputs);

            if (selection3 == null && selection2 == null && selection1 == null) {
                checkNotNull(valueMissing);
                log.warn("Insufficient value in wallet for send: needed {} more",
                        bitcoinValueToFriendlyString(valueMissing));
                throw new InsufficientMoneyException(valueMissing);
            }

            BigInteger lowestFee = null;
            bestCoinSelection = null;
            bestChangeOutput = null;
            if (selection1 != null) {
                if (selection1Change != null)
                    lowestFee = selection1.valueGathered.subtract(selection1Change.getValue());
                else
                    lowestFee = selection1.valueGathered;
                bestCoinSelection = selection1;
                bestChangeOutput = selection1Change;
            }

            if (selection2 != null) {
                BigInteger fee = selection2.valueGathered.subtract(checkNotNull(selection2Change).getValue());
                if (lowestFee == null || fee.compareTo(lowestFee) < 0) {
                    lowestFee = fee;
                    bestCoinSelection = selection2;
                    bestChangeOutput = selection2Change;
                }
            }

            if (selection3 != null) {
                if (lowestFee == null || selection3.valueGathered.compareTo(lowestFee) < 0) {
                    bestCoinSelection = selection3;
                    bestChangeOutput = null;
                }
            }
        }

        public FeeCalculation(SendRequest req, BigInteger value, List<TransactionInput> originalInputs,
                boolean needAtLeastReferenceFee, LinkedList<TransactionOutput> candidates)
                throws InsufficientMoneyException {
            checkState(lock.isHeldByCurrentThread());
            // There are 3 possibilities for what adding change might do:
            // 1) No effect
            // 2) Causes increase in fee (change < 0.01 COINS)
            // 3) Causes the transaction to have a dust output or change < fee increase (ie change will be thrown away)
            // If we get either of the last 2, we keep note of what the inputs looked like at the time and try to
            // add inputs as we go up the list (keeping track of minimum inputs for each category).  At the end, we pick
            // the best input set as the one which generates the lowest total fee.
            BigInteger additionalValueForNextCategory = null;
            CoinSelection selection3 = null;
            CoinSelection selection2 = null;
            TransactionOutput selection2Change = null;
            CoinSelection selection1 = null;
            TransactionOutput selection1Change = null;
            // We keep track of the last size of the transaction we calculated but only if the act of adding inputs and
            // change resulted in the size crossing a 1000 byte boundary. Otherwise it stays at zero.
            int lastCalculatedSize = 0;
            BigInteger valueNeeded, valueMissing = null;
            while (true) {
                resetTxInputs(req, originalInputs);

                BigInteger fees = req.fee == null ? BigInteger.ZERO : req.fee;
                if (lastCalculatedSize > 0) {
                    // If the size is exactly 1000 bytes then we'll over-pay, but this should be rare.
                    fees = fees.add(BigInteger.valueOf((lastCalculatedSize / 1000) + 1).multiply(req.feePerKb));
                } else {
                    fees = fees.add(req.feePerKb); // First time around the loop.
                }
                if (needAtLeastReferenceFee && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0)
                    fees = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;

                valueNeeded = value.add(fees);
                if (additionalValueForNextCategory != null)
                    valueNeeded = valueNeeded.add(additionalValueForNextCategory);
                BigInteger additionalValueSelected = additionalValueForNextCategory;

                // Of the coins we could spend, pick some that we actually will spend.
                CoinSelector selector = req.coinSelector == null ? coinSelector : req.coinSelector;
                CoinSelection selection = selector.select(valueNeeded, candidates);
                // Can we afford this?
                if (selection.valueGathered.compareTo(valueNeeded) < 0) {
                    valueMissing = valueNeeded.subtract(selection.valueGathered);
                    break;
                }
                checkState(selection.gathered.size() > 0 || originalInputs.size() > 0);

                // We keep track of an upper bound on transaction size to calculate fees that need to be added.
                // Note that the difference between the upper bound and lower bound is usually small enough that it
                // will be very rare that we pay a fee we do not need to.
                //
                // We can't be sure a selection is valid until we check fee per kb at the end, so we just store
                // them here temporarily.
                boolean eitherCategory2Or3 = false;
                boolean isCategory3 = false;

                BigInteger change = selection.valueGathered.subtract(valueNeeded);
                if (additionalValueSelected != null)
                    change = change.add(additionalValueSelected);

                // If change is < 0.01 BTC, we will need to have at least minfee to be accepted by the network
                if (req.ensureMinRequiredFee && !change.equals(BigInteger.ZERO) && change.compareTo(Utils.CENT) < 0
                        && fees.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
                    // This solution may fit into category 2, but it may also be category 3, we'll check that later
                    eitherCategory2Or3 = true;
                    additionalValueForNextCategory = Utils.CENT;
                    // If the change is smaller than the fee we want to add, this will be negative
                    change = change.subtract(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.subtract(fees));
                }

                int size = 0;
                TransactionOutput changeOutput = null;
                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. The address comes either from the request or getChangeAddress() as a default.
                    Address changeAddress = req.changeAddress;
                    if (changeAddress == null)
                        changeAddress = getChangeAddress();
                    changeOutput = new TransactionOutput(params, req.tx, change, changeAddress);
                    // If the change output would result in this transaction being rejected as dust, just drop the change and make it a fee
                    if (req.ensureMinRequiredFee && Transaction.MIN_NONDUST_OUTPUT.compareTo(change) >= 0) {
                        // This solution definitely fits in category 3
                        isCategory3 = true;
                        additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE
                                .add(Transaction.MIN_NONDUST_OUTPUT.add(BigInteger.ONE));
                    } else {
                        size += changeOutput.bitcoinSerialize().length + VarInt.sizeOf(req.tx.getOutputs().size())
                                - VarInt.sizeOf(req.tx.getOutputs().size() - 1);
                        // This solution is either category 1 or 2
                        if (!eitherCategory2Or3) // must be category 1
                            additionalValueForNextCategory = null;
                    }
                } else {
                    if (eitherCategory2Or3) {
                        // This solution definitely fits in category 3 (we threw away change because it was smaller than MIN_TX_FEE)
                        isCategory3 = true;
                        additionalValueForNextCategory = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE
                                .add(BigInteger.ONE);
                    }
                }

                // Now add unsigned inputs for the selected coins.
                for (TransactionOutput output : selection.gathered) {
                    TransactionInput input = req.tx.addInput(output);
                    // If the scriptBytes don't default to none, our size calculations will be thrown off.
                    checkState(input.getScriptBytes().length == 0);
                }

                // Estimate transaction size and loop again if we need more fee per kb. The serialized tx doesn't
                // include things we haven't added yet like input signatures/scripts or the change output.
                size += req.tx.bitcoinSerialize().length;
                size += estimateBytesForSigning(selection);
                if (size / 1000 > lastCalculatedSize / 1000 && req.feePerKb.compareTo(BigInteger.ZERO) > 0) {
                    lastCalculatedSize = size;
                    // We need more fees anyway, just try again with the same additional value
                    additionalValueForNextCategory = additionalValueSelected;
                    continue;
                }

                if (isCategory3) {
                    if (selection3 == null)
                        selection3 = selection;
                } else if (eitherCategory2Or3) {
                    // If we are in selection2, we will require at least CENT additional. If we do that, there is no way
                    // we can end up back here because CENT additional will always get us to 1
                    checkState(selection2 == null);
                    checkState(additionalValueForNextCategory.equals(Utils.CENT));
                    selection2 = selection;
                    selection2Change = checkNotNull(changeOutput); // If we get no change in category 2, we are actually in category 3
                } else {
                    // Once we get a category 1 (change kept), we should break out of the loop because we can't do better
                    checkState(selection1 == null);
                    checkState(additionalValueForNextCategory == null);
                    selection1 = selection;
                    selection1Change = changeOutput;
                }

                if (additionalValueForNextCategory != null) {
                    if (additionalValueSelected != null)
                        checkState(additionalValueForNextCategory.compareTo(additionalValueSelected) > 0);
                    continue;
                }
                break;
            }

            resetTxInputs(req, originalInputs);

            if (selection3 == null && selection2 == null && selection1 == null) {
                checkNotNull(valueMissing);
                log.warn("Insufficient value in wallet for send: needed {} more",
                        bitcoinValueToFriendlyString(valueMissing));
                throw new InsufficientMoneyException(valueMissing);
            }

            BigInteger lowestFee = null;
            bestCoinSelection = null;
            bestChangeOutput = null;
            if (selection1 != null) {
                if (selection1Change != null)
                    lowestFee = selection1.valueGathered.subtract(selection1Change.getValue());
                else
                    lowestFee = selection1.valueGathered;
                bestCoinSelection = selection1;
                bestChangeOutput = selection1Change;
            }

            if (selection2 != null) {
                BigInteger fee = selection2.valueGathered.subtract(checkNotNull(selection2Change).getValue());
                if (lowestFee == null || fee.compareTo(lowestFee) < 0) {
                    lowestFee = fee;
                    bestCoinSelection = selection2;
                    bestChangeOutput = selection2Change;
                }
            }

            if (selection3 != null) {
                if (lowestFee == null || selection3.valueGathered.compareTo(lowestFee) < 0) {
                    bestCoinSelection = selection3;
                    bestChangeOutput = null;
                }
            }
        }

        private void resetTxInputs(SendRequest req, List<TransactionInput> originalInputs) {
            req.tx.clearInputs();
            for (TransactionInput input : originalInputs)
                req.tx.addInput(input);
        }
    }

    private int estimateBytesForSigning(CoinSelection selection) {
        int size = 0;
        for (TransactionOutput output : selection.gathered) {
            try {
                if (output.getScriptPubKey().isSentToAddress()) {
                    // Send-to-address spends usually take maximum pubkey.length (as it may be compressed or not) + 75 bytes
                    final ECKey key = findKeyFromPubHash(output.getScriptPubKey().getPubKeyHash());
                    size += checkNotNull(key, "Coin selection includes unspendable outputs").getPubKey().length
                            + 75;
                } else if (output.getScriptPubKey().isSentToRawPubKey())
                    size += 74; // Send-to-pubkey spends usually take maximum 74 bytes to spend
                else
                    throw new IllegalStateException("Unknown output type returned in coin selection");
            } 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 IllegalStateException(e);
            }
        }
        return size;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //
    // Managing wallet-triggered transaction broadcast and key rotation.

    /**
     * <p>Specifies that the given {@link TransactionBroadcaster}, typically a {@link PeerGroup}, should be used for
     * sending transactions to the Bitcoin network by default. Some sendCoins methods let you specify a broadcaster
     * explicitly, in that case, they don't use this broadcaster. If null is specified then the wallet won't attempt
     * to broadcast transactions itself.</p>
     *
     * <p>You don't normally need to call this. A {@link PeerGroup} will automatically set itself as the wallets
     * broadcaster when you use {@link PeerGroup#addWallet(Wallet)}. A wallet can use the broadcaster when you ask
     * it to send money, but in future also at other times to implement various features that may require asynchronous
     * re-organisation of the wallet contents on the block chain. For instance, in future the wallet may choose to
     * optimise itself to reduce fees or improve privacy.</p>
     */
    public void setTransactionBroadcaster(@Nullable com.google.bitcoin.core.TransactionBroadcaster broadcaster) {
        Transaction[] toBroadcast = {};
        lock.lock();
        try {
            if (vTransactionBroadcaster == broadcaster)
                return;
            vTransactionBroadcaster = broadcaster;
            if (broadcaster == null)
                return;
            toBroadcast = pending.values().toArray(toBroadcast);
        } finally {
            lock.unlock();
        }
        // Now use it to upload any pending transactions we have that are marked as not being seen by any peers yet.
        // Don't hold the wallet lock whilst doing this, so if the broadcaster accesses the wallet at some point there
        // is no inversion.
        for (Transaction tx : toBroadcast) {
            checkState(tx.getConfidence().getConfidenceType() == ConfidenceType.PENDING);
            // Re-broadcast even if it's marked as already seen for two reasons
            // 1) Old wallets may have transactions marked as broadcast by 1 peer when in reality the network
            //    never saw it, due to bugs.
            // 2) It can't really hurt.
            log.info("New broadcaster so uploading waiting tx {}", tx.getHash());
            broadcaster.broadcastTransaction(tx);
        }
    }

    /**
     * When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
     * automatically respent to any key that was created after T. This can be used to recover from a situation where
     * a set of keys is believed to be compromised. Once the time is set transactions will be created and broadcast
     * immediately. New coins that come in after calling this method will be automatically respent immediately. The
     * rotation time is persisted to the wallet. You can stop key rotation by calling this method again with zero
     * as the argument.
     */
    public void setKeyRotationTime(Date time) {
        setKeyRotationTime(time.getTime() / 1000);
    }

    /**
     * Returns a UNIX time since the epoch in seconds, or zero if unconfigured.
     */
    public Date getKeyRotationTime() {
        return new Date(vKeyRotationTimestamp * 1000);
    }

    /**
     * <p>When a key rotation time is set, and money controlled by keys created before the given timestamp T will be
     * automatically respent to any key that was created after T. This can be used to recover from a situation where
     * a set of keys is believed to be compromised. Once the time is set transactions will be created and broadcast
     * immediately. New coins that come in after calling this method will be automatically respent immediately. The
     * rotation time is persisted to the wallet. You can stop key rotation by calling this method again with zero
     * as the argument, or by using {@link #setKeyRotationEnabled(boolean)}.</p>
     *
     * <p>Note that this method won't do anything unless you call {@link #setKeyRotationEnabled(boolean)} first.</p>
     */
    public void setKeyRotationTime(long unixTimeSeconds) {
        vKeyRotationTimestamp = unixTimeSeconds;
        if (unixTimeSeconds > 0) {
            log.info("Key rotation time set: {}", unixTimeSeconds);
            maybeRotateKeys();
        }
        saveNow();
    }

    /** Toggles key rotation on and off. Note that this state is not serialized. Activating it can trigger tx sends. */
    public void setKeyRotationEnabled(boolean enabled) {
        vKeyRotationEnabled = enabled;
        if (enabled)
            maybeRotateKeys();
    }

    /** Returns whether the keys creation time is before the key rotation time, if one was set. */
    public boolean isKeyRotating(ECKey key) {
        long time = vKeyRotationTimestamp;
        return time != 0 && key.getCreationTimeSeconds() < time;
    }

    // Checks to see if any coins are controlled by rotating keys and if so, spends them.
    private void maybeRotateKeys() {
        checkState(!lock.isHeldByCurrentThread());
        // TODO: Handle chain replays and encrypted wallets here.
        if (!vKeyRotationEnabled)
            return;
        // Snapshot volatiles so this method has an atomic view.
        long keyRotationTimestamp = vKeyRotationTimestamp;
        if (keyRotationTimestamp == 0)
            return; // Nothing to do.
        TransactionBroadcaster broadcaster = vTransactionBroadcaster;

        // Because transactions are size limited, we might not be able to re-key the entire wallet in one go. So
        // loop around here until we no longer produce transactions with the max number of inputs. That means we're
        // fully done, at least for now (we may still get more transactions later and this method will be reinvoked).
        Transaction tx;
        do {
            tx = rekeyOneBatch(keyRotationTimestamp, broadcaster);
        } while (tx != null && tx.getInputs().size() == KeyTimeCoinSelector.MAX_SIMULTANEOUS_INPUTS);
    }

    @Nullable
    private Transaction rekeyOneBatch(long keyRotationTimestamp, final TransactionBroadcaster broadcaster) {
        final Transaction rekeyTx;

        lock.lock();
        try {
            // Firstly, see if we have any keys that are beyond the rotation time, and any before.
            ECKey safeKey = null;
            boolean haveRotatingKeys = false;
            for (ECKey key : keychain) {
                final long t = key.getCreationTimeSeconds();
                if (t < keyRotationTimestamp) {
                    haveRotatingKeys = true;
                } else {
                    safeKey = key;
                }
            }
            if (!haveRotatingKeys)
                return null;
            if (safeKey == null) {
                log.warn("Key rotation requested but no keys newer than the timestamp are available.");
                return null;
            }
            // Build the transaction using some custom logic for our special needs. Last parameter to
            // KeyTimeCoinSelector is whether to ignore pending transactions or not.
            //
            // We ignore pending outputs because trying to rotate these is basically racing an attacker, and
            // we're quite likely to lose and create stuck double spends. Also, some users who have 0.9 wallets
            // have already got stuck double spends in their wallet due to the Bloom-filtering block reordering
            // bug that was fixed in 0.10, thus, making a re-key transaction depend on those would cause it to
            // never confirm at all.
            CoinSelector selector = new KeyTimeCoinSelector(this, keyRotationTimestamp, true);
            CoinSelection toMove = selector.select(BigInteger.ZERO, calculateAllSpendCandidates(true));
            if (toMove.valueGathered.equals(BigInteger.ZERO))
                return null; // Nothing to do.
            rekeyTx = new Transaction(params);
            for (TransactionOutput output : toMove.gathered) {
                rekeyTx.addInput(output);
            }
            rekeyTx.addOutput(toMove.valueGathered, safeKey);
            if (!adjustOutputDownwardsForFee(rekeyTx, toMove, BigInteger.ZERO,
                    Transaction.REFERENCE_DEFAULT_MIN_TX_FEE)) {
                log.error("Failed to adjust rekey tx for fees.");
                return null;
            }
            rekeyTx.getConfidence().setSource(TransactionConfidence.Source.SELF);
            rekeyTx.setPurpose(Transaction.Purpose.KEY_ROTATION);
            rekeyTx.signInputs(Transaction.SigHash.ALL, this);
            // KeyTimeCoinSelector should never select enough inputs to push us oversize.
            checkState(rekeyTx.bitcoinSerialize().length < Transaction.MAX_STANDARD_TX_SIZE);
            commitTx(rekeyTx);
        } catch (VerificationException e) {
            throw new RuntimeException(e); // Cannot happen.
        } finally {
            lock.unlock();
        }
        if (broadcaster == null)
            return rekeyTx;

        log.info("Attempting to send key rotation tx: {}", rekeyTx);
        // We must broadcast the tx in a separate thread to avoid inverting any locks. Otherwise we may be running
        // with the blockchain lock held (whilst receiving a block) and thus re-entering the peerGroup would invert
        // blockchain <-> peergroup.
        new Thread() {
            @Override
            public void run() {
                // Handle the future results just for logging.
                try {
                    Futures.addCallback(broadcaster.broadcastTransaction(rekeyTx),
                            new FutureCallback<Transaction>() {
                                @Override
                                public void onSuccess(Transaction transaction) {
                                    log.info("Successfully broadcast key rotation tx: {}", transaction);
                                }

                                @Override
                                public void onFailure(Throwable throwable) {
                                    log.error("Failed to broadcast key rotation tx", throwable);
                                }
                            });
                } catch (Exception e) {
                    log.error("Failed to broadcast rekey tx, will try again later", e);
                }
            }
        }.start();
        return rekeyTx;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /* CSPK-mike START */
    /**
     * CoinSpark object - wraps CoinSpark related calls
     */

    public CoinSpark CS;

    public class CoinSpark {

        private static final int OP_RETURN_MAXIMUM_LENGTH = 40;

        private CSAssetDatabase assetDB;
        private CSBalanceDatabase balanceDB;
        private CSMessageDatabase messageDB;

        // !!! Experimental
        private boolean canSendInvalidAssets;

        public boolean canSendInvalidAssets() {
            return canSendInvalidAssets;
        }

        public void setCanSendInvalidAssets(boolean canSendInvalidAssets) {
            this.canSendInvalidAssets = canSendInvalidAssets;
        }
        // !!! Experimental

        /**
         * CounSpark logger
         */

        public CSLogger log;
        private Wallet wallet;
        private Map<String, Long> mapRecentSends = new HashMap<String, Long>();

        private CoinSpark(Wallet ParentWallet) {
            wallet = ParentWallet;
        }

        /**
         * Opens CoinSpark databases.
         * @param FilePrefix Wallet files prefix
         * @return true on success, false on failure
         */

        public boolean initCSDatabases(String FilePrefix) {
            log = new CSLogger(FilePrefix + ".cslog");

            assetDB = new CSAssetDatabase(FilePrefix, log);

            balanceDB = new CSBalanceDatabase(FilePrefix, assetDB, log);

            messageDB = new CSMessageDatabase(FilePrefix, log, wallet);

            log.info("Wallet started");

            log.info("Blockchain height: " + lastBlockSeenHeight);

            int[] assetIDs = assetDB.getAssetIDs();

            if (assetIDs != null) {
                for (int assetID : assetIDs) {
                    CSAsset asset = assetDB.getAsset(assetID);
                    log.info("Asset " + assetID + ": " + asset.status());
                }
            }
            /*            
                        Map <Integer,BigInteger> mapQtys=CS.getAllUnspentAssetQuantities(true);                
                
                        for (Map.Entry<Integer, BigInteger> entry : mapQtys.entrySet()) 
                        {
            log.info("Qty Asset " + entry.getKey() + ": " + entry.getValue());
                        }        
              */
            return true;
        }

        public CSAssetDatabase getAssetDB() {
            return assetDB;
        }

        public CSBalanceDatabase getBalanceDB() {
            return balanceDB;
        }

        public CSMessageDatabase getMessageDB() {
            return messageDB;
        }

        private CoinSparkIORange findAssetInputRange(SendRequest req, int AssetID, BigInteger AssetValue,
                List<TransactionInput> originalInputs, LinkedList<TransactionOutput> candidates) {
            ArrayList<CSTransactionOutput> assetCandidates = new ArrayList<CSTransactionOutput>();
            ArrayList<CSTransactionOutput> selectedCandidates = new ArrayList<CSTransactionOutput>();

            int first, count, id;

            first = -1;
            count = 0;

            BigInteger valueNeeded = AssetValue;
            id = 0;
            for (TransactionInput input : originalInputs) {
                TransactionOutput output = input.getConnectedOutput();
                if (output == null) {
                    return null;
                }
                CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction().getHash(),
                        output.getIndex());
                CSBalance balance = balanceDB.getBalance(txOut, AssetID);
                if (balance != null) {
                    if ((balance.getState() == CSBalance.CSBalanceState.VALID)
                            || (balance.getState() == CSBalance.CSBalanceState.SELF)) {
                        valueNeeded = valueNeeded.subtract(balance.getQty());
                        if (first < 0) {
                            first = id;
                        }
                        count = id - first + 1;
                    }
                }
                id++;
            }

            if (valueNeeded.compareTo(BigInteger.ZERO) <= 0) {
                CoinSparkIORange iRange = new CoinSparkIORange();
                iRange.first = first;
                iRange.count = count;
                return iRange;
            }

            if (first < 0) {
                first = originalInputs.size();
                count = 0;
            }

            for (TransactionOutput output : candidates) {
                boolean inOriginalList = false;
                for (TransactionInput input : originalInputs) {
                    TransactionOutput inputTxOut = input.getConnectedOutput();
                    if (inputTxOut != null) {
                        if (inputTxOut.getParentTransaction().getHash()
                                .equals(output.parentTransaction.getHash())) {
                            if (inputTxOut.getIndex() == output.getIndex()) {
                                inOriginalList = true;
                            }
                        }
                    }
                }

                if (!inOriginalList) {
                    CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction(),
                            output.getIndex());
                    CSBalance balance = balanceDB.getBalance(txOut, AssetID);
                    if (balance != null) {
                        if ((balance.getState() == CSBalance.CSBalanceState.VALID)
                                || (balance.getState() == CSBalance.CSBalanceState.SELF)) {
                            txOut.setValue(balance.getQty());
                            assetCandidates.add(txOut);
                        }
                    }
                }
            }

            CSTransactionOutput.sortOutputs(assetCandidates);

            BigInteger assetTotal = BigInteger.ZERO;

            for (CSTransactionOutput output : assetCandidates) {
                if (assetTotal.compareTo(valueNeeded) >= 0) {
                    break;
                }
                if (!DefaultCoinSelector.isSelectable(output.getParentTransaction())) {
                    continue;
                }
                selectedCandidates.add(output);
                assetTotal = assetTotal.add(output.getValue());
            }

            if (assetTotal.compareTo(valueNeeded) < 0) {
                return null;
            }

            for (CSTransactionOutput assetOutput : selectedCandidates) {
                TransactionOutput foundOutput = null;
                for (TransactionOutput output : candidates) {
                    if (assetOutput.getParentTransaction().getHash().equals(output.parentTransaction.getHash())) {
                        if (assetOutput.getIndex() == output.getIndex()) {
                            req.tx.addInput(output);
                            foundOutput = output;
                            count++;
                        }
                    }
                }
                candidates.remove(foundOutput);
            }

            CoinSparkIORange iRange = new CoinSparkIORange();
            iRange.first = first;
            iRange.count = count;

            return iRange;
        }

        private boolean prepareMessage(SendRequest req) throws CSExceptions.CannotEncode {
            if (req.messageParts == null) {
                return true;
            }
            if (req.deliveryServers == null) {
                return false;
            }

            ECKey key = req.tx.getInput(0).getOutpoint().getConnectedKey(wallet);
            if (key == null) {
                return false;
            }
            Address pubKeyHash = new Address(wallet.getNetworkParameters(), key.getPubKeyHash());
            String sender = pubKeyHash.toString();

            key = null;
            Script connectedScript = req.tx.getOutput(0).getScriptPubKey();

            String recipient = null;

            if (connectedScript.isSentToAddress()) {
                recipient = connectedScript.getToAddress(wallet.getNetworkParameters()).toString();
            } else if (connectedScript.isSentToRawPubKey()) {
                byte[] pubKeyBytes = connectedScript.getPubKey();
                recipient = new Address(wallet.getNetworkParameters(), pubKeyBytes).toString();
            }

            if (recipient == null) {
                return false;
            }

            byte[] saltBytes = new byte[16];
            new Random().nextBytes(saltBytes);

            req.messageToCreate = new CSMessage(getMessageDB());

            if (req.paymentRef != null) {
                req.messageToCreate.setPaymentRef(req.paymentRef);
            }

            CSMessage.CSMessageParams messageParams = req.messageToCreate.new CSMessageParams();

            messageParams.isSent = true;
            messageParams.isPublic = false;
            messageParams.sender = sender;
            messageParams.keepseconds = req.KeepSeconds;
            messageParams.recipients = new String[] { recipient };
            messageParams.salt = Base64.encodeBase64String(saltBytes);

            req.messageToCreate.setAesKey(req.aesKey);
            req.messageToCreate.setMessageParams(messageParams);

            req.createNonce = new CSNonce();
            req.createNonce.error = CSUtils.CSServerError.SERVER_NOT_FOUND;

            byte[] txnMetaData = null;
            int metadataOutput = 0;
            int count = 0;
            for (TransactionOutput output : req.tx.getOutputs()) {
                byte[] scriptBytes = output.getScriptBytes();

                if (!CoinSparkBase.scriptIsRegular(scriptBytes)) {
                    txnMetaData = CoinSparkBase.scriptToMetadata(scriptBytes);
                    metadataOutput = count;
                }
                count++;
            }

            int appendMetadataMaxLen = OP_RETURN_MAXIMUM_LENGTH;

            if (txnMetaData != null) {
                appendMetadataMaxLen = CoinSparkBase.metadataMaxAppendLen(txnMetaData, OP_RETURN_MAXIMUM_LENGTH);
            }

            byte[] metadata = null;

            String[] randomServers = CSUtils.getRandomizedHTTPDeliveryServers(req.deliveryServers);

            StringBuilder sb = new StringBuilder(); // record server errors

            for (String serverURL : randomServers) {
                if (req.createNonce.error != CSUtils.CSServerError.NOERROR) {
                    req.messageToCreate.setServerURL(serverURL);
                    req.createNonce = req.messageToCreate.getCreateNonce(req.messageParts);
                    if (req.createNonce.error != CSUtils.CSServerError.NOERROR) {
                        CS.log.info("Server " + serverURL + ": " + req.createNonce.error.getCode() + " - "
                                + req.createNonce.errorMessage);
                        sb.append(serverURL + ": Error " + req.createNonce.error.getCode() + " - "
                                + req.createNonce.errorMessage + "\n");
                    } else {
                        req.message = new CoinSparkMessage();
                        if (!CSUtils.setDeliveryServer(req.messageToCreate.getActualServerURL(), req.message)) {
                            CS.log.info("Cannot parse server URL: " + req.messageToCreate.getActualServerURL());
                            sb.append(
                                    "Cannot parse server URL: " + req.messageToCreate.getActualServerURL() + "\n");
                        }
                        req.message.setIsPublic(false);
                        req.message.addOutputs(new CoinSparkIORange(0, 1));

                        int hashLen = req.message.calcHashLen(req.tx.getOutputs().size(), appendMetadataMaxLen);
                        req.message.setHashLen(hashLen);
                        byte[] hash = CoinSparkMessage.calcMessageHash(saltBytes, req.messageParts);
                        req.message.setHash(hash);

                        metadata = req.message.encode(req.tx.getOutputs().size(), appendMetadataMaxLen);

                        if (metadata == null) {
                            req.message = null;
                            req.createNonce.error = CSUtils.CSServerError.METADATA_ENCODE_ERROR;
                            CS.log.info("Cannot encode metadata for server " + req.messageToCreate.getServerURL());
                            sb.append("Cannot encode metadata for server " + req.messageToCreate.getServerURL()
                                    + "\n");
                        }
                    }
                }
            }

            if (req.createNonce.error != CSUtils.CSServerError.NOERROR) {
                CS.log.info("All delivery servers refused to create the message");
                throw new CSExceptions.CannotEncode(
                        "All delivery servers refused to create the message\n\n" + sb.toString());
            }

            if (metadata != null) {
                if (txnMetaData != null) {
                    metadata = CoinSparkBase.metadataAppend(txnMetaData, OP_RETURN_MAXIMUM_LENGTH, metadata);
                    if (metadata != null) {
                        changeMetadataInTx(metadata, req.tx, metadataOutput);
                    } else {
                        log.warning("Cannot encode message metadata");
                        throw new CSExceptions.CannotEncode("Cannot append message metadata");
                    }
                } else {
                    addMetadataToTx(metadata, req.tx);
                }
            } else {
                log.warning("Cannot encode message metadata");
                throw new CSExceptions.CannotEncode("Cannot encode message metadata");
            }

            log.info("Message metadata was successfully created.");

            return true;
        }

        private boolean preparePaymentRef(SendRequest req) {
            if (req.paymentRef == null) {
                return true;
            }

            byte[] txnMetaData = null;
            int metadataOutput = 0;
            int count = 0;
            for (TransactionOutput output : req.tx.getOutputs()) {
                byte[] scriptBytes = output.getScriptBytes();

                if (!CoinSparkBase.scriptIsRegular(scriptBytes)) {
                    txnMetaData = CoinSparkBase.scriptToMetadata(scriptBytes);
                    metadataOutput = count;
                }
                count++;
            }

            int appendMetadataMaxLen = OP_RETURN_MAXIMUM_LENGTH;

            if (txnMetaData != null) {
                appendMetadataMaxLen = CoinSparkBase.metadataMaxAppendLen(txnMetaData, OP_RETURN_MAXIMUM_LENGTH);
            }

            byte[] metadata = null;

            metadata = req.paymentRef.encode(appendMetadataMaxLen);

            if (metadata != null) {
                if (txnMetaData != null) {
                    metadata = CoinSparkBase.metadataAppend(txnMetaData, OP_RETURN_MAXIMUM_LENGTH, metadata);
                    if (metadata != null) {
                        changeMetadataInTx(metadata, req.tx, metadataOutput);
                    } else {
                        log.warning("Cannot encode paymentRef metadata");
                        return false;
                    }
                } else {
                    addMetadataToTx(metadata, req.tx);
                }
            } else {
                log.warning("Cannot encode paymentRef metadata");
                return false;
            }

            if (req.messageToCreate == null) {
                req.messageToCreate = new CSMessage(getMessageDB());
            }
            req.messageToCreate.setPaymentRef(req.paymentRef);

            log.info("PaymentRef metadata was successfully created.");

            return true;
        }

        private boolean createAssetTransfers(SendRequest req, List<TransactionInput> originalInputs,
                LinkedList<TransactionOutput> candidates) {
            req.transfers = null;

            if (req.assetTransfers == null) {
                return true;
            }

            log.info("Creating asset transfer metadata.");

            List<Integer> assetIDs = new ArrayList<Integer>();
            List<BigInteger> assetValues = new ArrayList<BigInteger>();

            for (SendRequest.CSAssetTransfer assetTransfer : req.assetTransfers) {

                CSAsset asset = assetDB.getAsset(assetTransfer.assetID);
                if ((asset.getAssetState() == CSAsset.CSAssetState.VALID || true == canSendInvalidAssets)
                        && asset.isAssetRefValid()) {
                    int i = assetIDs.indexOf(assetTransfer.assetID);
                    if (i < 0) {
                        assetIDs.add(assetTransfer.assetID);
                        assetValues.add(assetTransfer.value);
                    } else {
                        assetValues.set(i, assetValues.get(i).add(assetTransfer.value));
                    }
                } else {
                    log.warning("Cannot create transfer metadata for invalid asset " + assetTransfer.assetID);
                    return false;
                }
            }

            if (assetIDs.isEmpty()) {
                return true;
            }

            CoinSparkIORange[] assetIRanges = new CoinSparkIORange[assetIDs.size()];

            for (int i = 0; i < assetIDs.size(); i++) {
                CoinSparkIORange iRange = findAssetInputRange(req, assetIDs.get(i), assetValues.get(i),
                        originalInputs, candidates);
                if (iRange == null) {
                    log.warning("Not enough units for transfer of asset  " + assetIDs.get(i));
                    req.resetTxInputs(originalInputs);
                    return false;
                }

                assetIRanges[i] = iRange;
            }

            req.transfers = new CoinSparkTransferList(req.assetTransfers.size());

            int countOutputs = req.tx.getOutputs().size();
            long[] outputSatoshis = new long[countOutputs + 2];
            boolean[] outputRegular = new boolean[countOutputs + 2];

            int count = 0;
            for (TransactionOutput output : req.tx.getOutputs()) {
                outputSatoshis[count] = output.getValue().longValue();
                outputRegular[count] = true;
                count++;
            }

            outputSatoshis[countOutputs] = 0;
            outputRegular[countOutputs] = false;
            outputSatoshis[countOutputs + 1] = NetworkParameters.MAX_MONEY.longValue();
            outputRegular[countOutputs + 1] = true;
            /*            
                        BigInteger minOutput=BigInteger.valueOf(10000);
                        for(TransactionOutput output: req.tx.getOutputs())
                        {
            if(minOutput == null)
            {
                minOutput=output.getValue();                        
            }
            else
            {
                if(minOutput.compareTo(output.getValue()) > 0)
                {
                    minOutput=output.getValue();
                }
            }
                        }
            */
            int id = 0;

            req.assetsEncoded = new int[req.assetTransfers.size()];
            req.assetFee = BigInteger.ZERO;

            for (SendRequest.CSAssetTransfer assetTransfer : req.assetTransfers) {

                int i = assetIDs.indexOf(assetTransfer.assetID);
                CoinSparkTransfer transfer = new CoinSparkTransfer();
                transfer.setAssetRef(assetDB.getAsset(assetIDs.get(i)).getAssetReference());
                transfer.setInputs(assetIRanges[i]);
                transfer.setOutputs(new CoinSparkIORange(assetTransfer.first, assetTransfer.count));
                transfer.setQtyPerOutput(assetTransfer.value.longValue() / assetTransfer.count);

                //                req.assetFee=req.assetFee.add(minOutput);

                req.transfers.setTransfer(id, transfer);

                req.assetsEncoded[id] = assetTransfer.assetID;
                id++;
            }

            req.assetFee = BigInteger
                    .valueOf(req.transfers.calcMinFee(req.tx.getInputs().size(), outputSatoshis, outputRegular));

            byte[] metadata = req.transfers.encode(req.tx.getInputs().size(), req.tx.getOutputs().size(),
                    OP_RETURN_MAXIMUM_LENGTH);

            if (metadata != null) {
                addMetadataToTx(metadata, req.tx);
            } else {
                log.warning("Cannot encode transfer metadata");
                req.assetFee = BigInteger.ZERO;
                req.assetsEncoded = null;
                return false;
            }

            log.info("Transfer metadata was successfully created.");
            return true;
        }

        private boolean createAssetTransfersForEmptyWallet(SendRequest req, BigInteger MaxValue) {
            Map<Integer, BigInteger> assetMap = getAllUnspentAssetQuantities(false);

            int feePerAsset = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE.intValue();
            int maxAssets = MaxValue.divide(BigInteger.valueOf(feePerAsset)).intValue();

            if (maxAssets > assetMap.size() - 1) {
                maxAssets = assetMap.size() - 1;
            }

            if (maxAssets > 4) {
                maxAssets = 4;
            }

            if (maxAssets == 0) {
                return false;
            }

            log.info("Creating empty wallet transfer metadata.");

            int assetCount = 0;
            req.transfers = new CoinSparkTransferList(maxAssets);
            CoinSparkIORange iRange = new CoinSparkIORange(0, req.tx.getInputs().size());

            req.assetsEncoded = new int[maxAssets];
            req.assetFee = BigInteger.ZERO;

            for (Map.Entry<Integer, BigInteger> entry : assetMap.entrySet()) {
                int assetID = entry.getKey();
                CSAsset asset = null;
                if (assetID > 0) {
                    asset = assetDB.getAsset(assetID);
                }
                if ((asset != null) && (asset.isAssetRefValid()) && (assetCount < maxAssets)
                        && (entry.getValue().compareTo(BigInteger.ZERO) > 0)) {
                    CoinSparkTransfer transfer = new CoinSparkTransfer();
                    transfer.setAssetRef(assetDB.getAsset(assetID).getAssetReference());
                    transfer.setInputs(iRange);
                    transfer.setOutputs(new CoinSparkIORange(0, 1));
                    transfer.setQtyPerOutput(1);
                    req.assetFee = req.assetFee.add(BigInteger.valueOf(feePerAsset));

                    req.transfers.setTransfer(assetCount, transfer);

                    req.assetsEncoded[assetCount] = assetID;
                    assetCount++;
                }
            }

            byte[] metadata = req.transfers.encode(req.tx.getInputs().size(), req.tx.getOutputs().size(),
                    OP_RETURN_MAXIMUM_LENGTH);

            if (metadata != null) {
                addMetadataToTx(metadata, req.tx);
            } else {
                log.warning("Cannot encode transfer metadata");
                req.assetFee = BigInteger.ZERO;
                req.assetsEncoded = null;
                return false;
            }

            log.info("Transfer metadata was successfully created.");
            return true;
        }

        private void addMetadataToTx(byte[] metadata, Transaction tx) {
            tx.addOutput(new TransactionOutput(getParams(), tx, BigInteger.ZERO,
                    CoinSparkBase.metadataToScript(metadata)));
        }

        private void changeMetadataInTx(byte[] metadata, Transaction tx, int output_id) {
            List<TransactionOutput> originalOutputs = new ArrayList<TransactionOutput>(tx.getOutputs());
            //            List<TransactionOutput>originalOutputs=tx.getOutputs();
            int count = 0;
            tx.clearOutputs();
            for (TransactionOutput output : originalOutputs) {
                if (count == output_id) {
                    tx.addOutput(new TransactionOutput(getParams(), tx, BigInteger.ZERO,
                            CoinSparkBase.metadataToScript(metadata)));
                } else {
                    tx.addOutput(output);
                }
                count++;
            }
        }

        /**
         * Returns asset quantities send by current request.
         * @param req SendRequest object
         * @param AfterComplete if false - quantities which should be sent, if true - quantities actually sent
         * @return AssetID -> Quantity map
         */

        public Map<Integer, BigInteger> getAssetTransferValues(SendRequest req, boolean AfterComplete) {
            Map<Integer, BigInteger> map = new HashMap<Integer, BigInteger>();

            if (req.assetTransfers != null) {
                for (SendRequest.CSAssetTransfer assetTransfer : req.assetTransfers) {
                    boolean takeIt = true;

                    if (AfterComplete) {
                        takeIt = false;
                        for (int id : req.assetsEncoded) {
                            if (id == assetTransfer.assetID) {
                                takeIt = true;
                            }
                        }
                    }

                    if (takeIt) {
                        BigInteger value = BigInteger.ZERO;
                        if (map.containsKey(assetTransfer.assetID)) {
                            value = map.get(assetTransfer.assetID);
                        }
                        map.put(assetTransfer.assetID, value.add(assetTransfer.value));
                    }
                }
            } else {
                if (req.assetsEncoded != null) {
                    for (int id : req.assetsEncoded) {
                        BigInteger value = getUnspentAssetQuantity(id);
                        map.put(id, value);
                    }
                }
            }

            return map;
        }

        public class AssetBalance {
            public BigInteger total;
            public BigInteger spendable;
            public boolean updatingNow;
        }

        private AssetBalance getAssetBalance(LinkedList<TransactionOutput> candidates, int AssetID) {
            if (assetDB == null) {
                return null;
            }

            AssetBalance assetBalance = new AssetBalance();
            assetBalance.total = BigInteger.ZERO;
            assetBalance.spendable = BigInteger.ZERO;
            assetBalance.updatingNow = false;

            if (AssetID > 0) {
                CSAsset asset = assetDB.getAsset(AssetID);

                if (asset != null) {
                    for (TransactionOutput output : candidates) {
                        CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction(),
                                output.getIndex());
                        CSBalance balance = balanceDB.getBalance(txOut, AssetID);
                        if ((balance != null) && (balance.getQty() != null)) {
                            BigInteger value = balance.getQty();
                            if (value != null && (value.compareTo(BigInteger.ZERO) > 0)) {
                                if ((balance.getState() == CSBalance.CSBalanceState.NEVERCHECKED)
                                        || (balance.getState() == CSBalance.CSBalanceState.UNKNOWN)) {
                                    assetBalance.updatingNow = true;
                                    value = BigInteger.ZERO;
                                }
                            }
                            assetBalance.total = assetBalance.total.add(value);
                            if (output.isAvailableForSpending()
                                    && DefaultCoinSelector.isSelectable(output.getParentTransaction())) {
                                if (((balance.getState() == CSBalance.CSBalanceState.VALID)
                                        || (balance.getState() == CSBalance.CSBalanceState.SELF))
                                        && asset.isAssetRefValid()) {
                                    assetBalance.spendable = assetBalance.spendable.add(value);
                                }
                            }
                        }
                    }
                }
            } else {
                for (TransactionOutput output : candidates) {
                    BigInteger value = output.getValue();
                    assetBalance.total = assetBalance.total.add(value);
                    if (output.isAvailableForSpending()
                            && DefaultCoinSelector.isSelectable(output.getParentTransaction())) {
                        assetBalance.spendable = assetBalance.spendable.add(value);
                    }
                }
            }

            return assetBalance;
        }

        /**
         * Returns balance for given asset.
         * @param AssetID Asset ID in database
         * @return balance for given asset.
         */

        public AssetBalance getAssetBalance(int AssetID) {
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            return getAssetBalance(candidates, AssetID);
        }

        /**
         * Returns balances for all assets, including BTC (asset ID = 0)
         * @return balances for all assets
         */

        public Map<Integer, AssetBalance> getAllAssetBalances() {

            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            Map<Integer, AssetBalance> map = new HashMap<Integer, AssetBalance>();

            if (assetDB == null) {
                return map;
            }

            map.put(0, getAssetBalance(candidates, 0));

            int[] assetIDs = assetDB.getAssetIDs();

            if (assetIDs != null) {
                for (int assetID : assetIDs) {
                    map.put(assetID, getAssetBalance(candidates, assetID));
                }
            }

            return map;
        }

        private BigInteger getAssetQuantity(LinkedList<TransactionOutput> candidates, int AssetID,
                boolean OnlySpendable) {
            BigInteger total = BigInteger.ZERO;

            if (assetDB == null) {
                return total;
            }

            if (AssetID > 0) {
                CSAsset asset = assetDB.getAsset(AssetID);
                /*
                                if(asset.getAssetState() != CSAsset.CSAssetState.VALID)
                                {
                return total;
                                }
                */
                for (TransactionOutput output : candidates) {
                    CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction(),
                            output.getIndex());
                    CSBalance balance = balanceDB.getBalance(txOut, AssetID);
                    if (balance != null) {
                        if (!OnlySpendable || (((balance.getState() == CSBalance.CSBalanceState.VALID)
                                || (balance.getState() == CSBalance.CSBalanceState.SELF))
                                && output.isAvailableForSpending() && asset.isAssetRefValid()
                                && DefaultCoinSelector.isSelectable(output.getParentTransaction()))) {
                            if (balance.getQty() != null) {
                                if ((balance.getState() != CSBalance.CSBalanceState.NEVERCHECKED)
                                        || (balance.getState() != CSBalance.CSBalanceState.UNKNOWN)) {
                                    total = total.add(balance.getQty());
                                }
                            }
                        }
                        /*                        
                                                else
                                                {
                        if(balance.getQty() != null)
                        {
                            if(balance.getQty().longValue()>0)
                            {
                                CS.log.info("!!!! Unspendable txout: " +  AssetID + " - " + balance.getQty() + " - " + balance.getState() + " - " 
                                                + output.isAvailableForSpending() + " - " + asset.isAssetRefValid() + " - " + DefaultCoinSelector.isSelectable(output.getParentTransaction()));
                            }
                        }                            
                                                }
                                                */
                    }
                }
            } else {
                for (TransactionOutput output : candidates) {
                    total = total.add(output.getValue());
                }
            }

            return total;
        }

        public BigInteger getUnspentAssetQuantity(int AssetID) {
            return getUnspentAssetQuantity(AssetID, false);
        }

        /**
         * Returns unspent quantity for given asset.
         * @param AssetID Asset ID in database
         * @param OnlySpendable return only spendable, i.e. available for spending and VALID
         * @return unspent quantity for given asset.
         */

        public BigInteger getUnspentAssetQuantity(int AssetID, boolean OnlySpendable) {
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            return getAssetQuantity(candidates, AssetID, OnlySpendable);
        }

        /**
         * Returns totals for all assets, including BTC (asset ID = 0)
         * @param OnlySpendable return only spendable, i.e. available for spending and VALID
         * @return totals for all assets
         */

        public Map<Integer, BigInteger> getAllUnspentAssetQuantities(boolean OnlySpendable) {

            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            Map<Integer, BigInteger> map = new HashMap<Integer, BigInteger>();

            if (assetDB == null) {
                return map;
            }

            map.put(0, getAssetQuantity(candidates, 0, OnlySpendable));

            int[] assetIDs = assetDB.getAssetIDs();

            if (assetIDs != null) {
                for (int assetID : assetIDs) {
                    //                    if(assetDB.getAsset(assetID).getAssetState() == CSAsset.CSAssetState.VALID)
                    {
                        map.put(assetID, getAssetQuantity(candidates, assetID, OnlySpendable));
                    }
                }
            }

            return map;
        }

        private Map<Integer, BigInteger> getAssetsQuantities(List<TransactionOutput> outputs) {
            Map<Integer, BigInteger> map = new HashMap<Integer, BigInteger>();

            if (balanceDB == null) {
                return map;
            }

            map.put(0, BigInteger.ZERO);
            for (TransactionOutput output : outputs) {
                if (output.isMine(wallet)) {
                    map.put(0, map.get(0).add(output.getValue()));

                    CSTransactionOutput txOut = new CSTransactionOutput(output.parentTransaction,
                            output.getIndex());
                    CSBalanceDatabase.CSBalanceIterator iter = balanceDB.getTxOutBalances(txOut);

                    CSBalance balance = iter.next();
                    while (balance != null) {
                        if (balance.getAssetID() > 0) {
                            //                            if(balance.getState() == CSBalance.CSBalanceState.VALID)
                            switch (balance.getState()) {
                            case ZERO:
                                break;
                            case NEVERCHECKED: // We received this tx, but qty is not confirmed yet
                            case UNKNOWN:
                                if (balance.getQty() != null) {
                                    if (balance.getQty().longValue() > 0) // This is actual transfer, but value is unknown. projected value is ignored
                                    {
                                        BigInteger value = BigInteger.ZERO;
                                        if (map.containsKey(balance.getAssetID())) {
                                            value = map.get(balance.getAssetID());
                                        }
                                        map.put(balance.getAssetID(), value);
                                    }
                                }
                                break;
                            default:
                                BigInteger value = BigInteger.ZERO;
                                if (map.containsKey(balance.getAssetID())) {
                                    value = map.get(balance.getAssetID());
                                }
                                if (balance.getQty() != null) {
                                    value = value.add(balance.getQty());
                                }
                                if (value.compareTo(BigInteger.ZERO) > 0) {
                                    map.put(balance.getAssetID(), value);
                                }
                                break;
                            }
                        }
                        balance = iter.next();
                    }

                }
            }

            return map;
        }

        /**
         * Return asset quantities received by the wallet in transaction
         * @param tx transaction
         * @return AssetID -> Quantity map
         */

        public Map<Integer, BigInteger> getAssetsSentToMe(Transaction tx) {
            return getAssetsQuantities(tx.getOutputs());
        }

        /**
         * Return asset quantities send by the wallet in transaction
         * @param tx transaction
         * @return AssetID -> Quantity map
         */

        public Map<Integer, BigInteger> getAssetsSentFromMe(Transaction tx) {
            LinkedList<TransactionOutput> outputs = new LinkedList<TransactionOutput>();

            for (TransactionInput input : tx.getInputs()) {
                if (input.getConnectedOutput() != null) {
                    outputs.add(input.getConnectedOutput());
                }
            }

            return getAssetsQuantities(outputs);
        }

        /**
         * Calculate input balances of transactions for output balances sestimation
         * @param tx Transaction 
         * @return map AssetID->array of quantities
         */

        public Map<Integer, long[]> getInputAssetBalances(Transaction tx) {
            HashMap<Integer, long[]> inputBalances = new HashMap<Integer, long[]>();

            int size = tx.getInputs().size();
            int count = 0;

            long[] btcs = new long[size];
            for (int i = 0; i < size; i++) {
                btcs[i] = 0;
            }
            inputBalances.put(0, btcs);

            for (TransactionInput input : tx.getInputs()) {
                TransactionOutput output = input.getConnectedOutput();
                if (output == null) {
                    return null;
                }

                inputBalances.get(0)[count] = output.getValue().longValue();

                CSBalanceDatabase.CSBalanceIterator iter = balanceDB.getTxOutBalances(
                        new CSTransactionOutput(output.getParentTransaction(), output.getIndex()));

                CSBalance balance = iter.next();
                while (balance != null) {
                    int assetID = balance.getAssetID();
                    if (assetID > 0) {
                        if (!inputBalances.containsKey(assetID)) {
                            long[] qtys = new long[size];
                            for (int i = 0; i < size; i++) {
                                qtys[i] = 0;
                            }
                            inputBalances.put(assetID, qtys);
                        }
                        if (balance.getQty() != null) {
                            inputBalances.get(assetID)[count] = balance.getQty().longValue();
                        } else {
                            inputBalances.get(assetID)[count] = -1;
                        }
                    }
                    balance = iter.next();
                }
                count++;
            }

            count = 0;
            int[] assetsToRemove = new int[inputBalances.size()];

            for (Map.Entry<Integer, long[]> entry : inputBalances.entrySet()) {
                boolean removeIt = false;
                for (long qty : entry.getValue()) {
                    if (qty < 0) {
                        removeIt = true;
                    }
                }
                if (removeIt) {
                    assetsToRemove[count] = entry.getKey();
                    count++;
                }
            }

            for (int i = 0; i < count; i++) {
                inputBalances.remove(assetsToRemove[i]);
            }

            return inputBalances;
        }

        /**
         * Returns all unspent txouts having non-zero valid value for given asset.
         * @param AssetID
         * @return TxOut -> Quantity map
         */

        public Map<CSTransactionOutput, BigInteger> getAllUnspentAssetTxOuts(int AssetID) {
            LinkedList<TransactionOutput> candidates = calculateAllSpendCandidates(true);

            Map<CSTransactionOutput, BigInteger> map = new HashMap<CSTransactionOutput, BigInteger>();

            if (AssetID > 0) {
                CSAsset asset = assetDB.getAsset(AssetID);

                /*                
                                if(asset.getAssetState() != CSAsset.CSAssetState.VALID)
                                {
                return map;
                                }
                */
                for (TransactionOutput output : candidates) {
                    CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction(),
                            output.getIndex());
                    CSBalance balance = balanceDB.getBalance(txOut, AssetID);
                    if (balance != null) {
                        if ((balance.getState() == CSBalance.CSBalanceState.VALID)
                                || (balance.getState() == CSBalance.CSBalanceState.SELF)) {
                            map.put(txOut, balance.getQty());
                        }
                    }
                }
            } else {
                for (TransactionOutput output : candidates) {
                    map.put(new CSTransactionOutput(output.getParentTransaction(), output.getIndex()),
                            output.getValue());
                }
            }

            return map;
        }

        /**
         * Get the number of UTXO
         * @return 
         */
        public int getNumberUTXO() {
            lock.lock();
            int n = this.calculateAllTxOuts().size();
            lock.unlock();
            return n;
        }

        protected LinkedList<TransactionOutput> calculateAllTxOuts() {
            LinkedList<TransactionOutput> candidates = Lists.newLinkedList();
            for (Transaction tx : Iterables.concat(unspent.values(), pending.values())) {
                for (TransactionOutput output : tx.getOutputs()) {
                    if (!output.isMine(CS.wallet))
                        continue;
                    candidates.add(output);
                }
            }

            return candidates;
        }

        /**
         * Returns all unspent txouts having non-zero valid value for given asset.
         * @return TxOut -> Quantity map
         */

        public Map<CSTransactionOutput, Map<Integer, CSBalance>> getAllAssetTxOuts() {
            LinkedList<TransactionOutput> candidates = calculateAllTxOuts();

            Map<CSTransactionOutput, Map<Integer, CSBalance>> map = new HashMap<CSTransactionOutput, Map<Integer, CSBalance>>();

            for (TransactionOutput output : candidates) {
                CSTransactionOutput txOut = new CSTransactionOutput(output.getParentTransaction(),
                        output.getIndex());
                map.put(txOut, getTxOutBalances(txOut, output.getValue()));
            }

            return map;
        }

        private Map<Integer, CSBalance> getTxOutBalances(CSTransactionOutput TxOut, BigInteger BTCValue) {
            Map<Integer, CSBalance> map = new HashMap<Integer, CSBalance>();

            CSBalanceDatabase.CSBalanceIterator iter = balanceDB.getTxOutBalances(TxOut);

            map.put(0, new CSBalance(TxOut, 0, BTCValue, new Date(), 0, CSBalance.CSBalanceState.VALID));
            CSBalance balance = iter.next();
            while (balance != null) {
                map.put(balance.getAssetID(), balance);
                balance = iter.next();
            }

            return map;
        }

        /**
         * Returns the list of balances for specific txout, including BTC value (asset ID=0).
         * @param TxOut
         * @return AssetID -> CSBalance map
         */

        public Map<Integer, CSBalance> getTxOutBalances(CSTransactionOutput TxOut) {
            Map<Integer, CSBalance> map = new HashMap<Integer, CSBalance>();

            CSBalanceDatabase.CSBalanceIterator iter = balanceDB.getTxOutBalances(TxOut);

            CSBalance balance = iter.next();
            while (balance != null) {
                map.put(balance.getAssetID(), balance);
                balance = iter.next();
            }

            return map;
        }

        /**
         * Retrieves asset by ID from the database.
         * @param AssetID ID of the asset in the database
         * @return CSAsset object or null if not found
         */

        public CSAsset getAsset(int AssetID) {
            if (assetDB == null) {
                return null;
            }

            return assetDB.getAsset(AssetID);
        }

        /**
         * Inserts new asset into database.
         * @param Asset Asset to insert
         * @return stored Asset on success, null on failure
         */

        public CSAsset insertAsset(CSAsset Asset) {
            if (assetDB == null) {
                return null;
            }

            return assetDB.insertAsset(Asset);
        }

        /**
         * Deletes asset from asset and balance databases
         * @param Asset Asset to delete
         * @return true on success, false on failure
         */

        public boolean deleteAsset(CSAsset Asset) {
            if (assetDB == null) {
                return false;
            }

            if (Asset == null) {
                return false;
            }

            if (!assetDB.deleteAsset(Asset)) {
                return false;
            }

            if (balanceDB != null) {
                balanceDB.deleteAsset(Asset.getAssetID());
            }

            return true;
        }

        /**
         * Returns arrays of all Asset IDs in database
         * @return Array of Asset IDs
         */

        public int[] getAssetIDs() {
            if (assetDB == null) {
                return new int[0];
            }

            return assetDB.getAssetIDs();
        }

        /**
         * Validates all assets in the database.
         * @param pg PeerGroup
         */

        public void validateAllAssets(PeerGroup pg) {
            if (assetDB == null) {
                return;
            }

            assetDB.validateAssets(pg);
        }

        /**
         * Validates all assets in the database.
         * @param pg PeerGroup
         */

        public void retrieveMessages() {
            if (messageDB == null) {
                return;
            }

            messageDB.retrieveMessages();
        }

        /**
         * Refreshes and validates info for given asset.
         * @param AssetID Asset ID in database
         * @param pg PeerGroup
         */

        public void validateAsset(int AssetID, PeerGroup pg) {
            if (assetDB == null) {
                return;
            }

            assetDB.refreshAsset(AssetID, pg);
        }

        private Map<String, Integer> getTxDepthMap() {
            Map<String, Integer> map = new HashMap<String, Integer>();

            Set<Transaction> txs = getTransactions(true);

            for (Transaction tx : txs) {
                TransactionConfidence conf = tx.getConfidence();

                if (conf.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
                    map.put(tx.getHashAsString(), lastBlockSeenHeight - conf.getAppearedAtChainHeight());
                }
            }

            return map;
        }

        /**
         * Tries to calculate balances for all assets using tracking servers.
         * @return true on success, false on failure
         */

        public boolean calculateBalances() {
            if (balanceDB == null) {
                return false;
            }

            return balanceDB.calculateBalances(getTxDepthMap());
        }

        /**
         * Sets refresh flag for all balances for given TxOut.  
         * @param TxOut TxOut to refresh
         * @return  true on success, false on failure
         */

        public boolean setNeedsCalculateBalances(CSTransactionOutput TxOut) {
            if (balanceDB == null) {
                return false;
            }

            return balanceDB.refreshTxOut(TxOut);
        }

        /**
         * Sets refresh flag for  all balances for given asset.
         * @param AssetID asset ID in database
         * @return  true on success, false on failure
         */

        public boolean setNeedsCalculateBalances(int AssetID) {
            if (balanceDB == null) {
                return false;
            }

            return balanceDB.refreshAsset(AssetID);
        }

        /**
         * Sets refresh flag for  all balances using tracking servers.
         * @return  true on success, false on failure
         */

        public boolean setNeedsCalculateBalances() {
            if (balanceDB == null) {
                return false;
            }

            return balanceDB.refreshAll();
        }

        private void clearAssetRefs(int MinHeight, int MaxHeight) {
            if (assetDB == null) {
                return;
            }

            int[] assetIDs = assetDB.getAssetIDs();
            if (assetIDs == null)
                return;
            for (int assetID : assetIDs) {
                CSAsset asset = assetDB.getAsset(assetID);
                if (asset != null) {
                    if (asset.getAssetReference() != null) {
                        long assetHeight = asset.getAssetReference().getBlockNum();
                        if ((assetHeight >= MinHeight) && (assetHeight <= MaxHeight)) {
                            assetDB.clearAssetReference(asset);
                        }
                    }
                }
            }
        }

        private void addToRecentSends(Transaction tx) {
            for (TransactionInput input : tx.getInputs()) // Checking this transaction is actiual send
            {
                if (!tx.isTransactionInputMine(input, wallet)) {
                    return;
                }

            }

            long timeNow = new Date().getTime();

            Map<String, Long> newMap = new HashMap<String, Long>(); // Cleaning up old transactions
            for (Map.Entry<String, Long> entry : mapRecentSends.entrySet()) {
                if (entry.getValue() > timeNow - 2 * 86400) {
                    newMap.put(entry.getKey(), entry.getValue());
                }
            }

            log.info("Sent tx " + tx.getHashAsString() + " is added to recent send list");
            newMap.put(tx.getHashAsString(), timeNow);
            mapRecentSends = newMap;
        }

        public boolean isReplayToRecentSend(Transaction tx) {
            log.info("Checking new tx " + tx.getHashAsString() + " if it is replay to recent send");
            if (tx.getInputs().isEmpty()) {
                return false;
            }
            for (TransactionInput input : tx.getInputs()) {
                if (!mapRecentSends.containsKey(input.getOutpoint().getHash().toString())) {
                    return false;
                }
            }
            log.info("New tx " + tx.getHashAsString() + " is a replay to recent send");
            return true;
        }

    }

    public boolean initCSDatabases(String FilePrefix) {
        return CS.initCSDatabases(FilePrefix);
    }

    public String test() {
        CS.log.debug("Wallet test started");

        String s1;
        String s = "";

        s += "\n";

        MVMap blobMap = CSMessageDatabase.getBlobMap();
        List<String> keys = blobMap.keyList();
        for (String key : keys) {
            s += "BLOB MAP KEY FOUND: " + key + "\n";
        }
        s += "\n";

        s += "Unspent TxOuts\n\n";
        Map<CSTransactionOutput, Map<Integer, CSBalance>> mapTxOuts = CS.getAllAssetTxOuts();

        for (Map.Entry<CSTransactionOutput, Map<Integer, CSBalance>> entryTxOut : mapTxOuts.entrySet()) {
            s += "TxOut " + entryTxOut.getKey() + "\n";
            for (Map.Entry<Integer, CSBalance> entryBalance : entryTxOut.getValue().entrySet()) {
                s += " Asset: " + entryBalance.getKey() + ": " + entryBalance.getValue() + "\n";
            }
            CSMessage message = CS.messageDB.getMessage(entryTxOut.getKey().getTxID().toString());
            if (message != null) {
                if (message.getPaymentRef() != null) {
                    s += " PaymentRef: " + message.getPaymentRef().getRef() + "\n";
                }

                if (message.getMessageParts() != null) {
                    for (CSMessagePart messagePart : message.getMessageParts()) {
                        s += " Message: fileName:" + messagePart.fileName + ", Size: " + messagePart.contentSize
                                + "\n";
                        // Does data exist in blob store?
                        byte[] blob = CSMessageDatabase.getBlobForMessagePart(
                                entryTxOut.getKey().getTxID().toString(), messagePart.partID);
                        if (blob != null) {
                            s += "  Blob data found: " + messagePart.mimeType + "\n";
                            if (messagePart.mimeType.equals("text/plain")) {
                                try {
                                    String msg = new String(blob, "UTF-8");
                                    s += "Blob message: " + msg + "\n";
                                } catch (Exception e) {
                                    s += e.toString() + "\n";
                                }
                            }
                        }

                    }
                }

                s += " Metadata:\n" + message.metadataToString() + "\n";
            }
            s += "\n";
        }

        s += "\n";

        /*
                String pdfPath;
                String folderPath="/home/mike/tmp/pdfs/";
                String logFile="/home/mike/tmp/pdfs/pdfparser.log";
                File folder = new File(folderPath);
                File[] listOfFiles = folder.listFiles(); 
                RandomAccessFile lFile=null;
                try {
        lFile = new RandomAccessFile(logFile, "rw");
                } catch (FileNotFoundException ex) {
        java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                }
             
                for (int i = 0; i < listOfFiles.length; i++) 
        //        for (int i = 0; i < 10; i++) 
                { 
        if (listOfFiles[i].isFile()) 
        {
            pdfPath=folderPath+listOfFiles[i].getName();
            if(".pdf".equals(pdfPath.substring(pdfPath.length()-4, pdfPath.length())))
            {
                s1="";
                String s2="";
                int fileSize=0;
                RandomAccessFile aFile;
                try {
                    aFile = new RandomAccessFile(pdfPath, "r");
                    fileSize = (int)aFile.length();                    
                    if(fileSize > 0)
                    {
                        byte[] raw=new byte[fileSize];
            
                        if(!CSUtils.readFromFileToBytes(aFile,raw))
                        {
                            s+="ID: " + i + ", File: " + pdfPath + " Error: Cannot read file\n";
                            return null;            
                        }
            
                        CSPDFParser parser=new CSPDFParser(raw);
            
                        Date startTime=new Date();
                        String result= "SUCCESS";
                        boolean rerun=true;
                        boolean logObjects=false;
                        while(rerun)
                        {
                            rerun=false;
                            int next=0;
                            while(next<raw.length)
                            {
                                CSPDFParser.CSPDFObject obj;
                                try {
                                    obj = parser.getObject(next);
                                    if(obj != null)
                                    {
                                        if(logObjects)
                                        {
                                            try {
                                                lFile.writeBytes(obj.toString(String.format("%3d: ",i)) + "\n");
                                            } catch (IOException ex) {
                                                java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                                            }
        //                                                s2+=obj.toString() + "\n";
                                        }
                                        s+=obj.toString() + "\n";
                                        next=obj.offsetNext;
                                        if(obj.type == CSPDFParser.CSPDFObjectType.STRANGE)
                                        {
                                            result="FAILURE!!!";
                                            if(!logObjects)
                                            {
                                                rerun=true;
                                                logObjects=true;
                                            }
                                            next=raw.length;
                                        }
                                        else
                                        {
                                            CSPDFParser.CSPDFObjectEmbedded embedded=obj.hasEmbeddedFileOrURL();
                                            if(embedded != CSPDFParser.CSPDFObjectEmbedded.NONE)
                                            {
                                                try {
                                                    lFile.writeBytes("\n\n" + String.format("%03d: (%06x) !!!Embedded ",i,obj.offset) + embedded + "\n\n");
                                                } catch (IOException ex) {
                                                    java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                                                }
                                                if(!logObjects)
                                                {
                                                    rerun=true;
                                                    logObjects=true;                                                        
                                                    next=raw.length;
                                                }                                                    
                                            }
                                        }
                                    }
                                    else
                                    {
                                        next=raw.length;                        
                                    }
                                } catch (Exception ex) {
                                    s+="ERROR!!!\n";
                                    java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                                    next=raw.length;                        
                                }
                            }
                        }
                        s1=s2+"ID: " + i + ", File: " + pdfPath + ", Size: "+ fileSize + ", Time: " + (new Date().getTime()-startTime.getTime()) + "ms, Result: " + result;
                    }
            
                    aFile.close();
                } catch (FileNotFoundException ex) {
                    s1="ID: " + i + ", File: " + pdfPath + " Error: Cannot open file";
                } catch (IOException ex) {
                    s1="ID: " + i + ", File: " + pdfPath + " Error: Cannot get file size";
                }
                s+=s1+"\n";
        //                    CS.log.info(s1);        
                log.debug(s1);
                try {
                    lFile.writeBytes("\n" + s1 + "\n");
                } catch (IOException ex) {
                    java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                }
                    
            }
        }
                    
                }
            
          */
        /*        
                s+="Asset totals (spendable)\n\n";
                    
                Map <Integer,BigInteger> mapQtys=CS.getAllUnspentAssetQuantities(true);                
            
                for (Map.Entry<Integer, BigInteger> entry : mapQtys.entrySet()) 
                {
        s+="Qty Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
                }        
                    
                s+="\n";
                    
                s+="Asset totals (all)\n\n";
                    
                mapQtys=CS.getAllUnspentAssetQuantities(false);                
            
                for (Map.Entry<Integer, BigInteger> entry : mapQtys.entrySet()) 
                {
        s+="Qty Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
                }        
        */
        /*  
                s+="Asset totals \n\n";
                    
                Map <Integer,CoinSpark.AssetBalance> mapQtys=CS.getAllAssetBalances();                
            
                for (Map.Entry<Integer, CoinSpark.AssetBalance> entry : mapQtys.entrySet()) 
                {
        if(CS.assetDB.getAsset(entry.getKey()) != null)
        {
            s+="Qty Asset " + entry.getKey() + " (" + CS.assetDB.getAsset(entry.getKey()).getAssetReference().encode() + "): ";
        }
        else
        {
            s+="Qty Asset " + entry.getKey() + " (" +"No asset ref" + "): ";                
        }
        s+=entry.getValue().total;
        if(entry.getValue().updatingNow)
        {
            s+="+...";
        }
        if(entry.getValue().total.compareTo(entry.getValue().spendable) != 0)
        {
            s+=" (spendable " + entry.getValue().spendable + ")";
        }
        s+="\n";
                }        
                    
                s+="\n";
            
                s1=s;
                    
                s+="Asset txouts\n\n";
                
                Map <String,CSTransactionOutput> mapTxOuts= new HashMap<String,CSTransactionOutput>();
                    
                for (Map.Entry<Integer, CoinSpark.AssetBalance> entryAsset : mapQtys.entrySet()) 
                {            
        Map<CSTransactionOutput,BigInteger> mapAssetTxOuts=CS.getAllUnspentAssetTxOuts(entryAsset.getKey());
        s+="Asset " + entryAsset.getKey() + ": " + entryAsset.getValue().total + "\n";
            
        for (Map.Entry<CSTransactionOutput, BigInteger> entry : mapAssetTxOuts.entrySet()) 
        {
            s+="TxOut " + entry.getKey() + ": " + entry.getValue() + "\n";
            mapTxOuts.put(entry.getKey().toString(), entry.getKey());
        }        
            
        s+="\n";
                }
                    
                s+="\n";
                    
                    
                s+="TxOuts\n\n";
                    
                for (Map.Entry<String, CSTransactionOutput> entryTxOut : mapTxOuts.entrySet()) 
                {            
        Map<Integer,CSBalance> mapBalances=CS.getTxOutBalances(entryTxOut.getValue());
        s+="TxOut " + entryTxOut.getKey() + "\n";
            
        for (Map.Entry<Integer, CSBalance> entry : mapBalances.entrySet()) 
        {
            s+="Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
        }        
            
        s+="\n";
                }
                    
                s+="Transactions:\n\n";
                    
                Set<Transaction> txs=getTransactions(true);
                    
                for(Transaction tx : txs)
                {
        s+="Tx " + tx.getHashAsString() + "\n";
            
        s+="Sent:\n";
        Map <Integer,BigInteger> mapTxQtysFrom=CS.getAssetsSentFromMe(tx);                
            
        for (Map.Entry<Integer, BigInteger> entry : mapTxQtysFrom.entrySet()) 
        {
            s+="Qty Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
        }        
            
        s+="Received:\n";
        Map <Integer,BigInteger> mapTxQtysTo=CS.getAssetsSentToMe(tx);                
            
        for (Map.Entry<Integer, BigInteger> entry : mapTxQtysTo.entrySet()) 
        {
            s+="Qty Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
        }        
            
        s+="\n";
            
                }
            
                    
                s+="\n";
                    
                s+="Transaction depths\n\n";
                    
                Map <String,Integer> mapDepths= CS.getTxDepthMap();
                    
                for (Map.Entry<String, Integer> entry : mapDepths.entrySet()) 
                {
        s+="Tx  " + entry.getKey() + ": " + entry.getValue() + "\n";
                }        
          */

        /*        
                try {
        SendRequest req=SendRequest.to(new Address(params, "n1RQsYiWMWNH3XuEkuimVrzBHa7NLbtNrU"),new BigInteger("10000"),6, new BigInteger("10000"),1);
        completeTx(req, true);
                } catch (AddressFormatException ex) {
        java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                
                } catch (InsufficientMoneyException ex) {
        java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                }
          */

        /*        
                s+="\n";
                    
                s+="Wallet migration\n\n";
                    
                Address sendAddressObject;
                try {
        sendAddressObject = getChangeAddress();
        SendRequest sendRequest = SendRequest.emptyWallet(sendAddressObject);
        sendRequest.ensureMinRequiredFee = true;
        sendRequest.fee = BigInteger.ZERO;
        completeTx(sendRequest, false);
            
        s+="Wallet can be successfully migrated with fee: " + sendRequest.fee + "\n";
        s+="The following assets were transferred:\n\n";
            
        Map<Integer,BigInteger> mapTransferred = CS.getAssetTransferValues(sendRequest,true);
            
        for (Map.Entry<Integer,BigInteger> entry : mapTransferred.entrySet()) 
        {
            s+="Asset " + entry.getKey() + ": " + entry.getValue() + "\n";
        }            
            
        CSTransactionAssets txAssets=new CSTransactionAssets(sendRequest.tx);
            
        s+="\n";
        s+="Transfer list: \n\n";
            
        if(txAssets.getTransfers() != null)
        {
            s+=txAssets.getTransfers().toString() + "\n";
        }
        else
        {
            s+="Empty!!!\n";
        }
            
                } catch (InsufficientMoneyException ex) {
        java.util.logging.Logger.getLogger(Wallet.class.getName()).log(Level.SEVERE, null, ex);
                }
        */
        s += "\n";

        CS.log.debug("Wallet test completed");
        return s;
    }

    /* CSPK-mike END */

}