导航菜单

页面标题

页面副标题

imToken v3.28.8 - Wallet.java 源代码

正在查看: imToken v3.28.8 应用的 Wallet.java JAVA 源代码文件

本页面展示 JAVA 反编译生成的源代码文件,支持语法高亮显示。 仅供安全研究与技术分析使用,严禁用于任何非法用途。请遵守相关法律法规。


package org.bitcoinj.wallet;

import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
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 com.google.protobuf.ByteString;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import javax.annotation.Nullable;
import kotlin.jvm.internal.LongCompanionObject;
import net.jcip.annotations.GuardedBy;
import org.bitcoinj.core.AbstractBlockChain;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.BloomFilter;
import org.bitcoinj.core.Coin;
import org.bitcoinj.core.Context;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.FilteredBlock;
import org.bitcoinj.core.InsufficientMoneyException;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.Peer;
import org.bitcoinj.core.PeerFilterProvider;
import org.bitcoinj.core.ScriptException;
import org.bitcoinj.core.Sha256Hash;
import org.bitcoinj.core.StoredBlock;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.TransactionBag;
import org.bitcoinj.core.TransactionBroadcast;
import org.bitcoinj.core.TransactionBroadcaster;
import org.bitcoinj.core.TransactionConfidence;
import org.bitcoinj.core.TransactionInput;
import org.bitcoinj.core.TransactionOutPoint;
import org.bitcoinj.core.TransactionOutput;
import org.bitcoinj.core.UTXO;
import org.bitcoinj.core.UTXOProvider;
import org.bitcoinj.core.UTXOProviderException;
import org.bitcoinj.core.Utils;
import org.bitcoinj.core.VerificationException;
import org.bitcoinj.core.listeners.NewBestBlockListener;
import org.bitcoinj.core.listeners.ReorganizeListener;
import org.bitcoinj.core.listeners.TransactionConfidenceEventListener;
import org.bitcoinj.core.listeners.TransactionReceivedInBlockListener;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.KeyCrypter;
import org.bitcoinj.crypto.KeyCrypterScrypt;
import org.bitcoinj.script.Script;
import org.bitcoinj.script.ScriptBuilder;
import org.bitcoinj.script.ScriptChunk;
import org.bitcoinj.signers.LocalTransactionSigner;
import org.bitcoinj.signers.MissingSigResolutionSigner;
import org.bitcoinj.signers.TransactionSigner;
import org.bitcoinj.utils.BaseTaggableObject;
import org.bitcoinj.utils.ListenerRegistration;
import org.bitcoinj.utils.Threading;
import org.bitcoinj.wallet.KeyChain;
import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.RiskAnalysis;
import org.bitcoinj.wallet.WalletFiles;
import org.bitcoinj.wallet.WalletTransaction;
import org.bitcoinj.wallet.listeners.KeyChainEventListener;
import org.bitcoinj.wallet.listeners.ScriptsChangeEventListener;
import org.bitcoinj.wallet.listeners.WalletChangeEventListener;
import org.bitcoinj.wallet.listeners.WalletCoinsReceivedEventListener;
import org.bitcoinj.wallet.listeners.WalletCoinsSentEventListener;
import org.bitcoinj.wallet.listeners.WalletEventListener;
import org.bitcoinj.wallet.listeners.WalletReorganizeEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;

public class Wallet extends BaseTaggableObject implements NewBestBlockListener, TransactionReceivedInBlockListener, PeerFilterProvider, KeyBag, TransactionBag, ReorganizeListener {
    private static final int MINIMUM_BLOOM_DATA_LENGTH = 8;
    private static final Logger log = LoggerFactory.getLogger(Wallet.class);
    private boolean acceptRiskyTransactions;

    @GuardedBy("lock")
    private List<BalanceFutureRequest> balanceFutureRequests;
    private final AtomicInteger bloomFilterGuard;
    private final ArrayList<TransactionOutPoint> bloomOutPoints;
    private final CopyOnWriteArrayList<ListenerRegistration<WalletChangeEventListener>> changeListeners;
    protected CoinSelector coinSelector;
    private final CopyOnWriteArrayList<ListenerRegistration<WalletCoinsReceivedEventListener>> coinsReceivedListeners;
    private final CopyOnWriteArrayList<ListenerRegistration<WalletCoinsSentEventListener>> coinsSentListeners;
    private Map<Transaction, TransactionConfidence.Listener.ChangeReason> confidenceChanged;
    protected final Context context;
    private final Map<Sha256Hash, Transaction> dead;
    private String description;
    private final HashMap<String, WalletExtension> extensions;
    private boolean hardSaveOnNextBlock;
    private HashSet<Sha256Hash> ignoreNextNewBlock;
    private boolean insideReorg;

    @GuardedBy("keyChainGroupLock")
    private KeyChainGroup keyChainGroup;
    protected final ReentrantLock keyChainGroupLock;

    @Nullable
    private Sha256Hash lastBlockSeenHash;
    private int lastBlockSeenHeight;
    private long lastBlockSeenTimeSecs;
    protected final ReentrantLock lock;
    protected final HashSet<TransactionOutput> myUnspents;
    private int onWalletChangedSuppressions;
    protected final NetworkParameters params;
    private final Map<Sha256Hash, Transaction> pending;
    private final CopyOnWriteArrayList<ListenerRegistration<WalletReorganizeEventListener>> reorganizeListeners;
    private RiskAnalysis.Analyzer riskAnalyzer;
    private final LinkedHashMap<Sha256Hash, Transaction> riskDropped;
    private final CopyOnWriteArrayList<ListenerRegistration<ScriptsChangeEventListener>> scriptChangeListeners;

    @GuardedBy("lock")
    private List<TransactionSigner> signers;
    private final Map<Sha256Hash, Transaction> spent;
    private final CopyOnWriteArrayList<ListenerRegistration<TransactionConfidenceEventListener>> transactionConfidenceListeners;
    protected final Map<Sha256Hash, Transaction> transactions;
    private TransactionConfidence.Listener txConfidenceListener;
    private final Map<Sha256Hash, Transaction> unspent;
    protected volatile WalletFiles vFileManager;
    private volatile long vKeyRotationTimestamp;
    protected volatile TransactionBroadcaster vTransactionBroadcaster;

    @Nullable
    private volatile UTXOProvider vUTXOProvider;
    private int version;

    @GuardedBy("keyChainGroupLock")
    private Set<Script> watchedScripts;

    public enum BalanceType {
        ESTIMATED,
        AVAILABLE,
        ESTIMATED_SPENDABLE,
        AVAILABLE_SPENDABLE
    }

    public static class CompletionException extends RuntimeException {
    }

    public static class CouldNotAdjustDownwards extends CompletionException {
    }

    public static class DustySendRequested extends CompletionException {
    }

    public static class ExceededMaxTransactionSize extends CompletionException {
    }

    public enum MissingSigsMode {
        USE_OP_ZERO,
        USE_DUMMY_SIG,
        THROW
    }

    public static class MultipleOpReturnRequested extends CompletionException {
    }

    public static class SendResult {
        public TransactionBroadcast broadcast;
        public ListenableFuture<Transaction> broadcastComplete;
        public Transaction tx;
    }

    public Wallet(NetworkParameters networkParameters) {
        this(Context.getOrCreate(networkParameters));
    }

    public Wallet(Context context) {
        this(context, new KeyChainGroup(context.getParams()));
    }

    public static Wallet fromSeed(NetworkParameters networkParameters, DeterministicSeed deterministicSeed) {
        return new Wallet(networkParameters, new KeyChainGroup(networkParameters, deterministicSeed));
    }

    public static Wallet fromWatchingKey(NetworkParameters networkParameters, DeterministicKey deterministicKey) {
        return new Wallet(networkParameters, new KeyChainGroup(networkParameters, deterministicKey));
    }

    public static Wallet fromWatchingKeyB58(NetworkParameters networkParameters, String str, long j) {
        DeterministicKey deserializeB58 = DeterministicKey.deserializeB58(null, str, networkParameters);
        deserializeB58.setCreationTimeSeconds(j);
        return fromWatchingKey(networkParameters, deserializeB58);
    }

    public static Wallet fromKeys(NetworkParameters networkParameters, List<ECKey> list) {
        Iterator<ECKey> it = list.iterator();
        while (it.hasNext()) {
            Preconditions.checkArgument(!(it.next() instanceof DeterministicKey));
        }
        KeyChainGroup keyChainGroup = new KeyChainGroup(networkParameters);
        keyChainGroup.importKeys(list);
        return new Wallet(networkParameters, keyChainGroup);
    }

    public Wallet(NetworkParameters networkParameters, KeyChainGroup keyChainGroup) {
        this(Context.getOrCreate(networkParameters), keyChainGroup);
    }

    private Wallet(Context context, KeyChainGroup keyChainGroup) {
        this.lock = Threading.lock("wallet");
        this.keyChainGroupLock = Threading.lock("wallet-keychaingroup");
        this.myUnspents = Sets.newHashSet();
        this.riskDropped = new LinkedHashMap<Sha256Hash, Transaction>() {
            @Override
            protected boolean removeEldestEntry(Map.Entry<Sha256Hash, Transaction> entry) {
                return size() > 1000;
            }
        };
        this.changeListeners = new CopyOnWriteArrayList<>();
        this.coinsReceivedListeners = new CopyOnWriteArrayList<>();
        this.coinsSentListeners = new CopyOnWriteArrayList<>();
        this.reorganizeListeners = new CopyOnWriteArrayList<>();
        this.scriptChangeListeners = new CopyOnWriteArrayList<>();
        this.transactionConfidenceListeners = new CopyOnWriteArrayList<>();
        this.riskAnalyzer = DefaultRiskAnalysis.FACTORY;
        this.coinSelector = new DefaultCoinSelector();
        this.hardSaveOnNextBlock = false;
        this.balanceFutureRequests = Lists.newLinkedList();
        this.bloomOutPoints = Lists.newArrayList();
        this.bloomFilterGuard = new AtomicInteger(0);
        this.context = context;
        this.params = context.getParams();
        this.keyChainGroup = (KeyChainGroup) Preconditions.checkNotNull(keyChainGroup);
        if (this.params.getId().equals(NetworkParameters.ID_UNITTESTNET)) {
            this.keyChainGroup.setLookaheadSize(5);
        }
        if (this.keyChainGroup.numKeys() == 0) {
            this.keyChainGroup.createAndActivateNewHDChain();
        }
        this.watchedScripts = Sets.newHashSet();
        this.unspent = new HashMap();
        this.spent = new HashMap();
        this.pending = new HashMap();
        this.dead = new HashMap();
        this.transactions = new HashMap();
        this.extensions = new HashMap<>();
        this.confidenceChanged = new LinkedHashMap();
        this.signers = new ArrayList();
        addTransactionSigner(new LocalTransactionSigner());
        createTransientState();
    }

    private void createTransientState() {
        this.ignoreNextNewBlock = new HashSet<>();
        this.txConfidenceListener = new TransactionConfidence.Listener() {
            @Override
            public void onConfidenceChanged(TransactionConfidence transactionConfidence, TransactionConfidence.Listener.ChangeReason changeReason) {
                if (changeReason == TransactionConfidence.Listener.ChangeReason.SEEN_PEERS) {
                    Wallet.this.lock.lock();
                    try {
                        Wallet.this.checkBalanceFuturesLocked(null);
                        Wallet.this.queueOnTransactionConfidenceChanged(Wallet.this.getTransaction(transactionConfidence.getTransactionHash()));
                        Wallet.this.maybeQueueOnWalletChanged();
                    } finally {
                        Wallet.this.lock.unlock();
                    }
                }
            }
        };
        this.acceptRiskyTransactions = false;
    }

    public NetworkParameters getNetworkParameters() {
        return this.params;
    }

    public DeterministicKeyChain getActiveKeyChain() {
        return this.keyChainGroup.getActiveKeyChain();
    }

    public final void addTransactionSigner(TransactionSigner transactionSigner) {
        this.lock.lock();
        try {
            if (transactionSigner.isReady()) {
                this.signers.add(transactionSigner);
                return;
            }
            throw new IllegalStateException("Signer instance is not ready to be added into Wallet: " + transactionSigner.getClass());
        } finally {
            this.lock.unlock();
        }
    }

    public List<TransactionSigner> getTransactionSigners() {
        this.lock.lock();
        try {
            return ImmutableList.copyOf(this.signers);
        } finally {
            this.lock.unlock();
        }
    }

    public DeterministicKey currentKey(KeyChain.KeyPurpose keyPurpose) {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.currentKey(keyPurpose);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public DeterministicKey currentReceiveKey() {
        return currentKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
    }

    public Address currentAddress(KeyChain.KeyPurpose keyPurpose) {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.currentAddress(keyPurpose);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public Address currentReceiveAddress() {
        return currentAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
    }

    public DeterministicKey freshKey(KeyChain.KeyPurpose keyPurpose) {
        return freshKeys(keyPurpose, 1).get(0);
    }

    public List<DeterministicKey> freshKeys(KeyChain.KeyPurpose keyPurpose, int i) {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            List<DeterministicKey> freshKeys = this.keyChainGroup.freshKeys(keyPurpose, i);
            this.keyChainGroupLock.unlock();
            saveNow();
            return freshKeys;
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public DeterministicKey freshReceiveKey() {
        return freshKey(KeyChain.KeyPurpose.RECEIVE_FUNDS);
    }

    public Address freshAddress(KeyChain.KeyPurpose keyPurpose) {
        this.keyChainGroupLock.lock();
        try {
            Address freshAddress = this.keyChainGroup.freshAddress(keyPurpose);
            this.keyChainGroupLock.unlock();
            saveNow();
            return freshAddress;
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public Address freshReceiveAddress() {
        return freshAddress(KeyChain.KeyPurpose.RECEIVE_FUNDS);
    }

    public List<ECKey> getIssuedReceiveKeys() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.getActiveKeyChain().getIssuedReceiveKeys();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public List<Address> getIssuedReceiveAddresses() {
        List<ECKey> issuedReceiveKeys = getIssuedReceiveKeys();
        ArrayList arrayList = new ArrayList(issuedReceiveKeys.size());
        Iterator<ECKey> it = issuedReceiveKeys.iterator();
        while (it.hasNext()) {
            arrayList.add(it.next().toAddress(getParams()));
        }
        return arrayList;
    }

    public void upgradeToDeterministic(@Nullable KeyParameter keyParameter) throws DeterministicUpgradeRequiresPassword {
        this.keyChainGroupLock.lock();
        try {
            this.keyChainGroup.upgradeToDeterministic(this.vKeyRotationTimestamp, keyParameter);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean isDeterministicUpgradeRequired() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.isDeterministicUpgradeRequired();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    private void maybeUpgradeToHD() throws DeterministicUpgradeRequiresPassword {
        maybeUpgradeToHD(null);
    }

    @GuardedBy("keyChainGroupLock")
    private void maybeUpgradeToHD(@Nullable KeyParameter keyParameter) throws DeterministicUpgradeRequiresPassword {
        Preconditions.checkState(this.keyChainGroupLock.isHeldByCurrentThread());
        if (this.keyChainGroup.isDeterministicUpgradeRequired()) {
            log.info("Upgrade to HD wallets is required, attempting to do so.");
            try {
                upgradeToDeterministic(keyParameter);
            } catch (DeterministicUpgradeRequiresPassword e) {
                log.error("Failed to auto upgrade due to encryption. You should call wallet.upgradeToDeterministic with the users AES key to avoid this error.");
                throw e;
            }
        }
    }

    public List<Script> getWatchedScripts() {
        this.keyChainGroupLock.lock();
        try {
            return new ArrayList(this.watchedScripts);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean removeKey(ECKey eCKey) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.removeImportedKey(eCKey);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public int getKeyChainGroupSize() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.numKeys();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public int getKeyChainGroupCombinedKeyLookaheadEpochs() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.getCombinedKeyLookaheadEpochs();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public List<ECKey> getImportedKeys() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.getImportedKeys();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public Address currentChangeAddress() {
        return currentAddress(KeyChain.KeyPurpose.CHANGE);
    }

    public Address getChangeAddress() {
        return currentChangeAddress();
    }

    @Deprecated
    public boolean addKey(ECKey eCKey) {
        return importKey(eCKey);
    }

    public boolean importKey(ECKey eCKey) {
        return importKeys(Lists.newArrayList(new ECKey[]{eCKey})) == 1;
    }

    @Deprecated
    public int addKeys(List<ECKey> list) {
        return importKeys(list);
    }

    public int importKeys(List<ECKey> list) {
        checkNoDeterministicKeys(list);
        this.keyChainGroupLock.lock();
        try {
            int importKeys = this.keyChainGroup.importKeys(list);
            this.keyChainGroupLock.unlock();
            saveNow();
            return importKeys;
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    private void checkNoDeterministicKeys(List<ECKey> list) {
        Iterator<ECKey> it = list.iterator();
        while (it.hasNext()) {
            if (it.next() instanceof DeterministicKey) {
                throw new IllegalArgumentException("Cannot import HD keys back into the wallet");
            }
        }
    }

    public int importKeysAndEncrypt(List<ECKey> list, CharSequence charSequence) {
        this.keyChainGroupLock.lock();
        try {
            Preconditions.checkNotNull(getKeyCrypter(), "Wallet is not encrypted");
            return importKeysAndEncrypt(list, getKeyCrypter().deriveKey(charSequence));
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public int importKeysAndEncrypt(List<ECKey> list, KeyParameter keyParameter) {
        this.keyChainGroupLock.lock();
        try {
            checkNoDeterministicKeys(list);
            return this.keyChainGroup.importKeysAndEncrypt(list, keyParameter);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void addAndActivateHDChain(DeterministicKeyChain deterministicKeyChain) {
        this.keyChainGroupLock.lock();
        try {
            this.keyChainGroup.addAndActivateHDChain(deterministicKeyChain);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void setKeyChainGroupLookaheadSize(int i) {
        this.keyChainGroupLock.lock();
        try {
            this.keyChainGroup.setLookaheadSize(i);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public int getKeyChainGroupLookaheadSize() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.getLookaheadSize();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void setKeyChainGroupLookaheadThreshold(int i) {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            this.keyChainGroup.setLookaheadThreshold(i);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public int getKeyChainGroupLookaheadThreshold() {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.getLookaheadThreshold();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public DeterministicKey getWatchingKey() {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.getActiveKeyChain().getWatchingKey();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean isWatching() {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.isWatching();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean isAddressWatched(Address address) {
        return isWatchedScript(ScriptBuilder.createOutputScript(address));
    }

    public boolean addWatchedAddress(Address address) {
        return addWatchedAddresses(Lists.newArrayList(new Address[]{address}), Utils.currentTimeMillis() / 1000) == 1;
    }

    public boolean addWatchedAddress(Address address, long j) {
        return addWatchedAddresses(Lists.newArrayList(new Address[]{address}), j) == 1;
    }

    public int addWatchedAddresses(List<Address> list, long j) {
        ArrayList newArrayList = Lists.newArrayList();
        Iterator<Address> it = list.iterator();
        while (it.hasNext()) {
            Script createOutputScript = ScriptBuilder.createOutputScript(it.next());
            createOutputScript.setCreationTimeSeconds(j);
            newArrayList.add(createOutputScript);
        }
        return addWatchedScripts(newArrayList);
    }

    public int addWatchedScripts(List<Script> list) {
        this.keyChainGroupLock.lock();
        try {
            int i = 0;
            for (Script script : list) {
                if (this.watchedScripts.contains(script)) {
                    this.watchedScripts.remove(script);
                }
                if (script.getCreationTimeSeconds() == 0) {
                    log.warn("Adding a script to the wallet with a creation time of zero, this will disable the checkpointing optimization!    {}", script);
                }
                this.watchedScripts.add(script);
                i++;
            }
            if (i > 0) {
                queueOnScriptsChanged(list, true);
                saveNow();
            }
            return i;
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean removeWatchedAddress(Address address) {
        return removeWatchedAddresses(ImmutableList.of(address));
    }

    public boolean removeWatchedAddresses(List<Address> list) {
        ArrayList newArrayList = Lists.newArrayList();
        Iterator<Address> it = list.iterator();
        while (it.hasNext()) {
            newArrayList.add(ScriptBuilder.createOutputScript(it.next()));
        }
        return removeWatchedScripts(newArrayList);
    }

    public boolean removeWatchedScripts(List<Script> list) {
        this.lock.lock();
        try {
            for (Script script : list) {
                if (this.watchedScripts.contains(script)) {
                    this.watchedScripts.remove(script);
                }
            }
            queueOnScriptsChanged(list, false);
            saveNow();
            return true;
        } finally {
            this.lock.unlock();
        }
    }

    public List<Address> getWatchedAddresses() {
        this.keyChainGroupLock.lock();
        try {
            LinkedList linkedList = new LinkedList();
            for (Script script : this.watchedScripts) {
                if (script.isSentToAddress()) {
                    linkedList.add(script.getToAddress(this.params));
                }
            }
            return linkedList;
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Override
    @Nullable
    public ECKey findKeyFromPubHash(byte[] bArr) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.findKeyFromPubHash(bArr);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean hasKey(ECKey eCKey) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.hasKey(eCKey);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Override
    public boolean isPubKeyHashMine(byte[] bArr) {
        return findKeyFromPubHash(bArr) != null;
    }

    @Override
    public boolean isWatchedScript(Script script) {
        this.keyChainGroupLock.lock();
        try {
            return this.watchedScripts.contains(script);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Override
    @Nullable
    public ECKey findKeyFromPubKey(byte[] bArr) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.findKeyFromPubKey(bArr);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Override
    public boolean isPubKeyMine(byte[] bArr) {
        return findKeyFromPubKey(bArr) != null;
    }

    @Override
    @Nullable
    public RedeemData findRedeemDataFromScriptHash(byte[] bArr) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.findRedeemDataFromScriptHash(bArr);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Override
    public boolean isPayToScriptHashMine(byte[] bArr) {
        return findRedeemDataFromScriptHash(bArr) != null;
    }

    private void markKeysAsUsed(Transaction transaction) {
        this.keyChainGroupLock.lock();
        try {
            Iterator<TransactionOutput> it = transaction.getOutputs().iterator();
            while (it.hasNext()) {
                try {
                    Script scriptPubKey = it.next().getScriptPubKey();
                    if (scriptPubKey.isSentToRawPubKey()) {
                        this.keyChainGroup.markPubKeyAsUsed(scriptPubKey.getPubKey());
                    } else if (scriptPubKey.isSentToAddress()) {
                        this.keyChainGroup.markPubKeyHashAsUsed(scriptPubKey.getPubKeyHash());
                    } else if (scriptPubKey.isPayToScriptHash()) {
                        this.keyChainGroup.markP2SHAddressAsUsed(Address.fromP2SHScript(transaction.getParams(), scriptPubKey));
                    }
                } catch (ScriptException e) {
                    log.warn("Could not parse tx output script: {}", e.toString());
                }
            }
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public DeterministicSeed getKeyChainSeed() {
        this.keyChainGroupLock.lock();
        try {
            DeterministicSeed seed = this.keyChainGroup.getActiveKeyChain().getSeed();
            if (seed != null) {
                return seed;
            }
            throw new ECKey.MissingPrivateKeyException();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public DeterministicKey getKeyByPath(List<ChildNumber> list) {
        this.keyChainGroupLock.lock();
        try {
            maybeUpgradeToHD();
            return this.keyChainGroup.getActiveKeyChain().getKeyByPath(list, false);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void encrypt(CharSequence charSequence) {
        this.keyChainGroupLock.lock();
        try {
            KeyCrypterScrypt keyCrypterScrypt = new KeyCrypterScrypt();
            this.keyChainGroup.encrypt(keyCrypterScrypt, keyCrypterScrypt.deriveKey(charSequence));
            this.keyChainGroupLock.unlock();
            saveNow();
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public void encrypt(KeyCrypter keyCrypter, KeyParameter keyParameter) {
        this.keyChainGroupLock.lock();
        try {
            this.keyChainGroup.encrypt(keyCrypter, keyParameter);
            this.keyChainGroupLock.unlock();
            saveNow();
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public void decrypt(CharSequence charSequence) {
        this.keyChainGroupLock.lock();
        try {
            KeyCrypter keyCrypter = this.keyChainGroup.getKeyCrypter();
            Preconditions.checkState(keyCrypter != null, "Not encrypted");
            this.keyChainGroup.decrypt(keyCrypter.deriveKey(charSequence));
            this.keyChainGroupLock.unlock();
            saveNow();
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public void decrypt(KeyParameter keyParameter) {
        this.keyChainGroupLock.lock();
        try {
            this.keyChainGroup.decrypt(keyParameter);
            this.keyChainGroupLock.unlock();
            saveNow();
        } catch (Throwable th) {
            this.keyChainGroupLock.unlock();
            throw th;
        }
    }

    public boolean checkPassword(CharSequence charSequence) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.checkPassword(charSequence);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean checkAESKey(KeyParameter keyParameter) {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.checkAESKey(keyParameter);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Nullable
    public KeyCrypter getKeyCrypter() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.getKeyCrypter();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public Protos.Wallet.EncryptionType getEncryptionType() {
        Protos.Wallet.EncryptionType encryptionType;
        this.keyChainGroupLock.lock();
        try {
            KeyCrypter keyCrypter = this.keyChainGroup.getKeyCrypter();
            if (keyCrypter != null) {
                encryptionType = keyCrypter.getUnderstoodEncryptionType();
            } else {
                encryptionType = Protos.Wallet.EncryptionType.UNENCRYPTED;
            }
            return encryptionType;
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public boolean isEncrypted() {
        return getEncryptionType() != Protos.Wallet.EncryptionType.UNENCRYPTED;
    }

    public void changeEncryptionPassword(CharSequence charSequence, CharSequence charSequence2) {
        this.keyChainGroupLock.lock();
        try {
            decrypt(charSequence);
            encrypt(charSequence2);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void changeEncryptionKey(KeyCrypter keyCrypter, KeyParameter keyParameter, KeyParameter keyParameter2) {
        this.keyChainGroupLock.lock();
        try {
            decrypt(keyParameter);
            encrypt(keyCrypter, keyParameter2);
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public List<Protos.Key> serializeKeyChainGroupToProtobuf() {
        this.keyChainGroupLock.lock();
        try {
            return this.keyChainGroup.serializeToProtobuf();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void saveToFile(File file, File file2) throws IOException {
        FileOutputStream fileOutputStream;
        this.lock.lock();
        FileOutputStream fileOutputStream2 = null;
        try {
            try {
                fileOutputStream = new FileOutputStream(file);
            } catch (Throwable th) {
                th = th;
            }
        } catch (RuntimeException e) {
            e = e;
        }
        try {
            saveToFileStream(fileOutputStream);
            fileOutputStream.flush();
            fileOutputStream.getFD().sync();
            fileOutputStream.close();
            if (Utils.isWindows()) {
                File canonicalFile = file2.getCanonicalFile();
                if (canonicalFile.exists() && !canonicalFile.delete()) {
                    throw new IOException("Failed to delete canonical wallet file for replacement with autosave");
                }
                if (!file.renameTo(canonicalFile)) {
                    throw new IOException("Failed to rename " + file + " to " + canonicalFile);
                }
                this.lock.unlock();
                if (file.exists()) {
                    log.warn("Temp file still exists after failed save.");
                    return;
                }
                return;
            }
            if (!file.renameTo(file2)) {
                throw new IOException("Failed to rename " + file + " to " + file2);
            }
            this.lock.unlock();
            if (file.exists()) {
                log.warn("Temp file still exists after failed save.");
            }
        } catch (RuntimeException e2) {
            e = e2;
            log.error("Failed whilst saving wallet", e);
            throw e;
        } catch (Throwable th2) {
            th = th2;
            fileOutputStream2 = fileOutputStream;
            this.lock.unlock();
            if (fileOutputStream2 != null) {
                fileOutputStream2.close();
            }
            if (file.exists()) {
                log.warn("Temp file still exists after failed save.");
            }
            throw th;
        }
    }

    public void saveToFile(File file) throws IOException {
        saveToFile(File.createTempFile("wallet", null, file.getAbsoluteFile().getParentFile()), file);
    }

    public void setAcceptRiskyTransactions(boolean z) {
        this.lock.lock();
        try {
            this.acceptRiskyTransactions = z;
        } finally {
            this.lock.unlock();
        }
    }

    public boolean isAcceptRiskyTransactions() {
        this.lock.lock();
        try {
            return this.acceptRiskyTransactions;
        } finally {
            this.lock.unlock();
        }
    }

    public void setRiskAnalyzer(RiskAnalysis.Analyzer analyzer) {
        this.lock.lock();
        try {
            this.riskAnalyzer = (RiskAnalysis.Analyzer) Preconditions.checkNotNull(analyzer);
        } finally {
            this.lock.unlock();
        }
    }

    public RiskAnalysis.Analyzer getRiskAnalyzer() {
        this.lock.lock();
        try {
            return this.riskAnalyzer;
        } finally {
            this.lock.unlock();
        }
    }

    public WalletFiles autosaveToFile(File file, long j, TimeUnit timeUnit, @Nullable WalletFiles.Listener listener) {
        this.lock.lock();
        try {
            Preconditions.checkState(this.vFileManager == null, "Already auto saving this wallet.");
            WalletFiles walletFiles = new WalletFiles(this, file, j, timeUnit);
            if (listener != null) {
                walletFiles.setListener(listener);
            }
            this.vFileManager = walletFiles;
            return walletFiles;
        } finally {
            this.lock.unlock();
        }
    }

    public void shutdownAutosaveAndWait() {
        this.lock.lock();
        try {
            WalletFiles walletFiles = this.vFileManager;
            this.vFileManager = null;
            Preconditions.checkState(walletFiles != null, "Auto saving not enabled.");
            walletFiles.shutdownAndWait();
        } finally {
            this.lock.unlock();
        }
    }

    protected void saveLater() {
        WalletFiles walletFiles = this.vFileManager;
        if (walletFiles != null) {
            walletFiles.saveLater();
        }
    }

    protected void saveNow() {
        WalletFiles walletFiles = this.vFileManager;
        if (walletFiles != null) {
            try {
                walletFiles.saveNow();
            } catch (IOException e) {
                log.error("Failed to save wallet to disk!", e);
                Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Threading.uncaughtExceptionHandler;
                if (uncaughtExceptionHandler != null) {
                    uncaughtExceptionHandler.uncaughtException(Thread.currentThread(), e);
                }
            }
        }
    }

    public void saveToFileStream(OutputStream outputStream) throws IOException {
        this.lock.lock();
        try {
            new WalletProtobufSerializer().writeWallet(this, outputStream);
        } finally {
            this.lock.unlock();
        }
    }

    public NetworkParameters getParams() {
        return this.params;
    }

    public Context getContext() {
        return this.context;
    }

    public static Wallet loadFromFile(File file, @Nullable WalletExtension... walletExtensionArr) throws UnreadableWalletException {
        FileInputStream fileInputStream;
        FileInputStream fileInputStream2 = null;
        try {
            try {
                fileInputStream = new FileInputStream(file);
            } catch (Throwable th) {
                th = th;
            }
            try {
                Wallet loadFromFileStream = loadFromFileStream(fileInputStream, walletExtensionArr);
                fileInputStream.close();
                return loadFromFileStream;
            } catch (Throwable th2) {
                th = th2;
                fileInputStream2 = fileInputStream;
                if (fileInputStream2 != null) {
                    fileInputStream2.close();
                }
                throw th;
            }
        } catch (IOException e) {
            throw new UnreadableWalletException("Could not open file", e);
        }
    }

    public boolean isConsistent() {
        try {
            isConsistentOrThrow();
            return true;
        } catch (IllegalStateException e) {
            log.error(e.getMessage());
            try {
                log.error(toString());
                return false;
            } catch (RuntimeException e2) {
                log.error("Printing inconsistent wallet failed", e2);
                return false;
            }
        }
    }

    public void isConsistentOrThrow() throws IllegalStateException {
        this.lock.lock();
        try {
            Set<Transaction> transactions = getTransactions(true);
            HashSet hashSet = new HashSet();
            Iterator<Transaction> it = transactions.iterator();
            while (it.hasNext()) {
                hashSet.add(it.next().getHash());
            }
            int size = transactions.size();
            if (size != hashSet.size()) {
                throw new IllegalStateException("Two transactions with same hash");
            }
            int size2 = this.unspent.size() + this.spent.size() + this.pending.size() + this.dead.size();
            if (size != size2) {
                throw new IllegalStateException("Inconsistent wallet sizes: " + size + ", " + size2);
            }
            for (Transaction transaction : this.unspent.values()) {
                if (!isTxConsistent(transaction, false)) {
                    throw new IllegalStateException("Inconsistent unspent tx: " + transaction.getHashAsString());
                }
            }
            for (Transaction transaction2 : this.spent.values()) {
                if (!isTxConsistent(transaction2, true)) {
                    throw new IllegalStateException("Inconsistent spent tx: " + transaction2.getHashAsString());
                }
            }
        } finally {
            this.lock.unlock();
        }
    }

    boolean isTxConsistent(Transaction transaction, boolean z) {
        boolean z2 = true;
        for (TransactionOutput transactionOutput : transaction.getOutputs()) {
            if (transactionOutput.isAvailableForSpending()) {
                if (transactionOutput.isMineOrWatched(this)) {
                    z2 = false;
                }
                if (transactionOutput.getSpentBy() != null) {
                    log.error("isAvailableForSpending != spentBy");
                    return false;
                }
            } else if (transactionOutput.getSpentBy() == null) {
                log.error("isAvailableForSpending != spentBy");
                return false;
            }
        }
        return z2 == z;
    }

    public static Wallet loadFromFileStream(InputStream inputStream, @Nullable WalletExtension... walletExtensionArr) throws UnreadableWalletException {
        Wallet readWallet = new WalletProtobufSerializer().readWallet(inputStream, walletExtensionArr);
        if (!readWallet.isConsistent()) {
            log.error("Loaded an inconsistent wallet");
        }
        return readWallet;
    }

    @Override
    public boolean notifyTransactionIsInBlock(Sha256Hash sha256Hash, StoredBlock storedBlock, AbstractBlockChain.NewBlockType newBlockType, int i) throws VerificationException {
        boolean z;
        this.lock.lock();
        try {
            Transaction transaction = this.transactions.get(sha256Hash);
            if (transaction == null) {
                transaction = this.riskDropped.get(sha256Hash);
                if (transaction == null) {
                    z = false;
                    return z;
                }
                log.info("Risk analysis dropped tx {} but was included in block anyway", transaction.getHash());
            }
            receive(transaction, storedBlock, newBlockType, i);
            z = true;
            return z;
        } finally {
            this.lock.unlock();
        }
    }

    public void receivePending(Transaction transaction, @Nullable List<Transaction> list, boolean z) throws VerificationException {
        this.lock.lock();
        try {
            transaction.verify();
            if (!getContainingPools(transaction).equals(EnumSet.noneOf(WalletTransaction.Pool.class))) {
                log.debug("Received tx we already saw in a block or created ourselves: " + transaction.getHashAsString());
            } else if (z || isPendingTransactionRelevant(transaction)) {
                if (isTransactionRisky(transaction, list) && !this.acceptRiskyTransactions) {
                    this.riskDropped.put(transaction.getHash(), transaction);
                    log.warn("There are now {} risk dropped transactions being kept in memory", Integer.valueOf(this.riskDropped.size()));
                } else {
                    Coin valueSentToMe = transaction.getValueSentToMe(this);
                    Coin valueSentFromMe = transaction.getValueSentFromMe(this);
                    if (log.isInfoEnabled()) {
                        log.info(String.format(Locale.US, "Received a pending transaction %s that spends %s from our own wallet, and sends us %s", transaction.getHashAsString(), valueSentFromMe.toFriendlyString(), valueSentToMe.toFriendlyString()));
                    }
                    if (transaction.getConfidence().getSource().equals(TransactionConfidence.Source.UNKNOWN)) {
                        log.warn("Wallet received transaction with an unknown source. Consider tagging it!");
                    }
                    commitTx(transaction);
                }
            }
        } finally {
            this.lock.unlock();
        }
    }

    public boolean isTransactionRisky(Transaction transaction, @Nullable List<Transaction> list) {
        boolean z;
        this.lock.lock();
        if (list == null) {
            try {
                list = ImmutableList.of();
            } finally {
                this.lock.unlock();
            }
        }
        RiskAnalysis create = this.riskAnalyzer.create(this, transaction, list);
        if (create.analyze() != RiskAnalysis.Result.OK) {
            log.warn("Pending transaction was considered risky: {}\n{}", create, transaction);
            z = true;
        } else {
            z = false;
        }
        return z;
    }

    public void receivePending(Transaction transaction, @Nullable List<Transaction> list) throws VerificationException {
        receivePending(transaction, list, false);
    }

    public boolean isPendingTransactionRelevant(Transaction transaction) throws ScriptException {
        this.lock.lock();
        try {
            if (!getContainingPools(transaction).equals(EnumSet.noneOf(WalletTransaction.Pool.class))) {
                log.debug("Received tx we already saw in a block or created ourselves: " + transaction.getHashAsString());
            } else if (!isTransactionRelevant(transaction)) {
                log.debug("Received tx that isn't relevant to this wallet, discarding.");
            } else {
                return true;
            }
            return false;
        } finally {
            this.lock.unlock();
        }
    }

    public boolean isTransactionRelevant(Transaction transaction) throws ScriptException {
        boolean z;
        this.lock.lock();
        try {
            if (transaction.getValueSentFromMe(this).signum() <= 0 && transaction.getValueSentToMe(this).signum() <= 0) {
                if (findDoubleSpendsAgainst(transaction, this.transactions).isEmpty()) {
                    z = false;
                    return z;
                }
            }
            z = true;
            return z;
        } finally {
            this.lock.unlock();
        }
    }

    private Set<Transaction> findDoubleSpendsAgainst(Transaction transaction, Map<Sha256Hash, Transaction> map) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        if (transaction.isCoinBase()) {
            return Sets.newHashSet();
        }
        HashSet hashSet = new HashSet();
        Iterator<TransactionInput> it = transaction.getInputs().iterator();
        while (it.hasNext()) {
            hashSet.add(it.next().getOutpoint());
        }
        HashSet newHashSet = Sets.newHashSet();
        for (Transaction transaction2 : map.values()) {
            Iterator<TransactionInput> it2 = transaction2.getInputs().iterator();
            while (it2.hasNext()) {
                if (hashSet.contains(it2.next().getOutpoint())) {
                    newHashSet.add(transaction2);
                }
            }
        }
        return newHashSet;
    }

    void addTransactionsDependingOn(Set<Transaction> set, Set<Transaction> set2) {
        LinkedHashMap linkedHashMap = new LinkedHashMap();
        for (Transaction transaction : set) {
            linkedHashMap.put(transaction.getHash(), transaction);
        }
        while (!linkedHashMap.isEmpty()) {
            Transaction transaction2 = (Transaction) linkedHashMap.remove(linkedHashMap.keySet().iterator().next());
            for (Transaction transaction3 : set2) {
                if (!transaction3.equals(transaction2)) {
                    Iterator<TransactionInput> it = transaction3.getInputs().iterator();
                    while (it.hasNext()) {
                        if (it.next().getOutpoint().getHash().equals(transaction2.getHash()) && linkedHashMap.get(transaction3.getHash()) == null) {
                            linkedHashMap.put(transaction3.getHash(), transaction3);
                            set.add(transaction3);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void receiveFromBlock(Transaction transaction, StoredBlock storedBlock, AbstractBlockChain.NewBlockType newBlockType, int i) throws VerificationException {
        this.lock.lock();
        try {
            if (isTransactionRelevant(transaction)) {
                receive(transaction, storedBlock, newBlockType, i);
            }
        } finally {
            this.lock.unlock();
        }
    }

    private void receive(Transaction transaction, StoredBlock storedBlock, AbstractBlockChain.NewBlockType newBlockType, int i) throws VerificationException {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Coin balance = getBalance();
        Sha256Hash hash = transaction.getHash();
        boolean z = newBlockType == AbstractBlockChain.NewBlockType.BEST_CHAIN;
        boolean z2 = newBlockType == AbstractBlockChain.NewBlockType.SIDE_CHAIN;
        Coin subtract = transaction.getValueSentToMe(this).subtract(transaction.getValueSentFromMe(this));
        Logger logger = log;
        Object[] objArr = new Object[5];
        objArr[0] = z2 ? " on a side chain" : "";
        objArr[1] = subtract.toFriendlyString();
        objArr[2] = transaction.getHashAsString();
        objArr[3] = Integer.valueOf(i);
        objArr[4] = storedBlock != null ? storedBlock.getHeader().getHash() : "(unit test)";
        logger.info("Received tx{} for {}: {} [{}] in block {}", objArr);
        markKeysAsUsed(transaction);
        this.onWalletChangedSuppressions++;
        Transaction transaction2 = this.transactions.get(transaction.getHash());
        if (transaction2 != null) {
            transaction = transaction2;
        }
        boolean z3 = this.pending.remove(hash) != null;
        if (z3) {
            log.info("  <-pending");
        }
        if (z) {
            boolean z4 = this.dead.remove(hash) != null;
            if (z4) {
                log.info("  <-dead");
            }
            if (z3) {
                for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                    TransactionInput spentBy = transactionOutput.getSpentBy();
                    if (spentBy != null) {
                        Preconditions.checkState(this.myUnspents.add(transactionOutput));
                        spentBy.disconnect();
                    }
                }
            }
            processTxFromBestChain(transaction, z3 || z4);
        } else {
            Preconditions.checkState(z2);
            if (z3) {
                addWalletTransaction(WalletTransaction.Pool.PENDING, transaction);
                log.info("  ->pending");
            } else {
                Sha256Hash hash2 = transaction.getHash();
                if (!this.unspent.containsKey(hash2) && !this.spent.containsKey(hash2) && !this.dead.containsKey(hash2)) {
                    commitTx(transaction);
                }
            }
        }
        if (storedBlock != null) {
            transaction.setBlockAppearance(storedBlock, z, i);
            if (z) {
                this.ignoreNextNewBlock.add(hash);
                HashSet newHashSet = Sets.newHashSet(new Transaction[]{transaction});
                addTransactionsDependingOn(newHashSet, getTransactions(true));
                newHashSet.remove(transaction);
                for (Transaction transaction3 : sortTxnsByDependency(newHashSet)) {
                    if (transaction3.getConfidence().getConfidenceType().equals(TransactionConfidence.ConfidenceType.IN_CONFLICT) && isNotSpendingTxnsInConfidenceType(transaction3, TransactionConfidence.ConfidenceType.IN_CONFLICT)) {
                        transaction3.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.PENDING);
                        this.confidenceChanged.put(transaction3, TransactionConfidence.Listener.ChangeReason.TYPE);
                    }
                }
            }
        }
        this.onWalletChangedSuppressions--;
        if (z) {
            this.confidenceChanged.put(transaction, TransactionConfidence.Listener.ChangeReason.TYPE);
        } else {
            maybeQueueOnWalletChanged();
        }
        if (!this.insideReorg && z) {
            Coin balance2 = getBalance();
            log.info("Balance is now: " + balance2.toFriendlyString());
            if (!z3) {
                int signum = subtract.signum();
                if (signum > 0) {
                    queueOnCoinsReceived(transaction, balance, balance2);
                } else if (signum < 0) {
                    queueOnCoinsSent(transaction, balance, balance2);
                }
            }
            checkBalanceFuturesLocked(balance2);
        }
        informConfidenceListenersIfNotReorganizing();
        isConsistentOrThrow();
        saveLater();
        this.hardSaveOnNextBlock = true;
    }

    private boolean isNotSpendingTxnsInConfidenceType(Transaction transaction, TransactionConfidence.ConfidenceType confidenceType) {
        Iterator<TransactionInput> it = transaction.getInputs().iterator();
        while (it.hasNext()) {
            Transaction transaction2 = getTransaction(it.next().getOutpoint().getHash());
            if (transaction2 != null && transaction2.getConfidence().getConfidenceType().equals(confidenceType)) {
                return false;
            }
        }
        return true;
    }

    List<Transaction> sortTxnsByDependency(Set<Transaction> set) {
        int i;
        boolean z;
        ArrayList arrayList = new ArrayList(set);
        int i2 = 0;
        while (i2 < arrayList.size() - 1) {
            do {
                i = i2 + 1;
                int i3 = i;
                while (true) {
                    if (i3 >= arrayList.size()) {
                        z = false;
                        break;
                    }
                    if (spends((Transaction) arrayList.get(i2), (Transaction) arrayList.get(i3))) {
                        arrayList.add(i3, (Transaction) arrayList.remove(i2));
                        z = true;
                        break;
                    }
                    i3++;
                }
            } while (z);
            i2 = i;
        }
        return arrayList;
    }

    boolean spends(Transaction transaction, Transaction transaction2) {
        Iterator<TransactionInput> it = transaction.getInputs().iterator();
        while (it.hasNext()) {
            if (it.next().getOutpoint().getHash().equals(transaction2.getHash())) {
                return true;
            }
        }
        return false;
    }

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

    @Override
    public void notifyNewBestBlock(StoredBlock storedBlock) throws VerificationException {
        Sha256Hash hash = storedBlock.getHeader().getHash();
        if (hash.equals(getLastBlockSeenHash())) {
            return;
        }
        this.lock.lock();
        try {
            setLastBlockSeenHash(hash);
            setLastBlockSeenHeight(storedBlock.getHeight());
            setLastBlockSeenTimeSecs(storedBlock.getHeader().getTimeSeconds());
            for (Transaction transaction : getTransactions(true)) {
                if (this.ignoreNextNewBlock.contains(transaction.getHash())) {
                    this.ignoreNextNewBlock.remove(transaction.getHash());
                } else {
                    TransactionConfidence confidence = transaction.getConfidence();
                    if (confidence.getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
                        if (confidence.incrementDepthInBlocks() > this.context.getEventHorizon()) {
                            confidence.clearBroadcastBy();
                        }
                        this.confidenceChanged.put(transaction, TransactionConfidence.Listener.ChangeReason.DEPTH);
                    }
                }
            }
            informConfidenceListenersIfNotReorganizing();
            maybeQueueOnWalletChanged();
            if (this.hardSaveOnNextBlock) {
                saveNow();
                this.hardSaveOnNextBlock = false;
            } else {
                saveLater();
            }
        } finally {
            this.lock.unlock();
        }
    }

    private void processTxFromBestChain(Transaction transaction, boolean z) throws VerificationException {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Preconditions.checkState(!this.pending.containsKey(transaction.getHash()));
        if (transaction.isCoinBase() && this.dead.containsKey(transaction.getHash())) {
            log.info("  coinbase tx <-dead: confidence {}", transaction.getHashAsString(), transaction.getConfidence().getConfidenceType().name());
            this.dead.remove(transaction.getHash());
        }
        updateForSpends(transaction, true);
        if (transaction.getValueSentToMe(this).signum() > 0) {
            if (transaction.isEveryOwnedOutputSpent(this)) {
                log.info("  tx {} ->spent (by pending)", transaction.getHashAsString());
                addWalletTransaction(WalletTransaction.Pool.SPENT, transaction);
            } else {
                log.info("  tx {} ->unspent", transaction.getHashAsString());
                addWalletTransaction(WalletTransaction.Pool.UNSPENT, transaction);
            }
        } else if (transaction.getValueSentFromMe(this).signum() > 0) {
            log.info("  tx {} ->spent", transaction.getHashAsString());
            addWalletTransaction(WalletTransaction.Pool.SPENT, transaction);
        } else if (z) {
            log.info("  tx {} ->spent (manually added)", transaction.getHashAsString());
            addWalletTransaction(WalletTransaction.Pool.SPENT, transaction);
        }
        Set<Transaction> findDoubleSpendsAgainst = findDoubleSpendsAgainst(transaction, this.pending);
        if (findDoubleSpendsAgainst.isEmpty()) {
            return;
        }
        killTxns(findDoubleSpendsAgainst, transaction);
    }

    private void updateForSpends(Transaction transaction, boolean z) throws VerificationException {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        if (z) {
            Preconditions.checkState(!this.pending.containsKey(transaction.getHash()));
        }
        for (TransactionInput transactionInput : transaction.getInputs()) {
            TransactionInput.ConnectionResult connect = transactionInput.connect(this.unspent, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
            if (connect != TransactionInput.ConnectionResult.NO_SUCH_TX || (connect = transactionInput.connect(this.spent, TransactionInput.ConnectMode.ABORT_ON_CONFLICT)) != TransactionInput.ConnectionResult.NO_SUCH_TX || (connect = transactionInput.connect(this.pending, TransactionInput.ConnectMode.ABORT_ON_CONFLICT)) != TransactionInput.ConnectionResult.NO_SUCH_TX) {
                TransactionOutput transactionOutput = (TransactionOutput) Preconditions.checkNotNull(transactionInput.getConnectedOutput());
                if (connect == TransactionInput.ConnectionResult.ALREADY_SPENT) {
                    if (!z) {
                        log.warn("Saw two pending transactions double spend each other");
                        log.warn("  offending input is input {}", Integer.valueOf(transaction.getInputs().indexOf(transactionInput)));
                        log.warn("{}: {}", transaction.getHash(), Utils.HEX.encode(transaction.unsafeBitcoinSerialize()));
                        Transaction parentTransaction = transactionOutput.getSpentBy().getParentTransaction();
                        log.warn("{}: {}", parentTransaction.getHash(), Utils.HEX.encode(parentTransaction.unsafeBitcoinSerialize()));
                    }
                } else if (connect == TransactionInput.ConnectionResult.SUCCESS) {
                    Transaction transaction2 = (Transaction) Preconditions.checkNotNull(transactionInput.getConnectedTransaction());
                    log.info("  marked {} as spent by {}", transactionInput.getOutpoint(), transaction.getHashAsString());
                    maybeMovePool(transaction2, "prevtx");
                    if (transactionOutput.isMineOrWatched(this)) {
                        Preconditions.checkState(this.myUnspents.remove(transactionOutput));
                    }
                }
            }
        }
        for (Transaction transaction3 : this.pending.values()) {
            for (TransactionInput transactionInput2 : transaction3.getInputs()) {
                TransactionInput.ConnectionResult connect2 = transactionInput2.connect(transaction, TransactionInput.ConnectMode.ABORT_ON_CONFLICT);
                if (z) {
                    Preconditions.checkState(connect2 != TransactionInput.ConnectionResult.ALREADY_SPENT);
                }
                if (connect2 == TransactionInput.ConnectionResult.SUCCESS) {
                    log.info("Connected pending tx input {}:{}", transaction3.getHashAsString(), Integer.valueOf(transaction3.getInputs().indexOf(transactionInput2)));
                    if (this.myUnspents.remove(transactionInput2.getConnectedOutput())) {
                        log.info("Removed from UNSPENTS: {}", transactionInput2.getConnectedOutput());
                    }
                }
            }
        }
        if (z) {
            return;
        }
        maybeMovePool(transaction, "pendingtx");
    }

    private void killTxns(Set<Transaction> set, @Nullable Transaction transaction) {
        LinkedList linkedList = new LinkedList(set);
        while (!linkedList.isEmpty()) {
            Transaction transaction2 = (Transaction) linkedList.poll();
            log.warn("TX {} killed{}", transaction2.getHashAsString(), transaction != null ? " by " + transaction.getHashAsString() : "");
            log.warn("Disconnecting each input and moving connected transactions.");
            this.pending.remove(transaction2.getHash());
            this.unspent.remove(transaction2.getHash());
            this.spent.remove(transaction2.getHash());
            addWalletTransaction(WalletTransaction.Pool.DEAD, transaction2);
            for (TransactionInput transactionInput : transaction2.getInputs()) {
                Transaction connectedTransaction = transactionInput.getConnectedTransaction();
                if (connectedTransaction != null) {
                    if (connectedTransaction.getConfidence().getConfidenceType() != TransactionConfidence.ConfidenceType.DEAD && transactionInput.getConnectedOutput().getSpentBy() != null && transactionInput.getConnectedOutput().getSpentBy().equals(transactionInput)) {
                        Preconditions.checkState(this.myUnspents.add(transactionInput.getConnectedOutput()));
                        log.info("Added to UNSPENTS: {} in {}", transactionInput.getConnectedOutput(), transactionInput.getConnectedOutput().getParentTransaction().getHash());
                    }
                    transactionInput.disconnect();
                    maybeMovePool(connectedTransaction, "kill");
                }
            }
            transaction2.getConfidence().setOverridingTransaction(transaction);
            this.confidenceChanged.put(transaction2, TransactionConfidence.Listener.ChangeReason.TYPE);
            for (TransactionOutput transactionOutput : transaction2.getOutputs()) {
                if (this.myUnspents.remove(transactionOutput)) {
                    log.info("XX Removed from UNSPENTS: {}", transactionOutput);
                }
                TransactionInput spentBy = transactionOutput.getSpentBy();
                if (spentBy != null) {
                    Transaction parentTransaction = spentBy.getParentTransaction();
                    log.info("This death invalidated dependent tx {}", parentTransaction.getHash());
                    linkedList.push(parentTransaction);
                }
            }
        }
        if (transaction == null) {
            return;
        }
        log.warn("Now attempting to connect the inputs of the overriding transaction.");
        for (TransactionInput transactionInput2 : transaction.getInputs()) {
            if (transactionInput2.connect(this.unspent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT) == TransactionInput.ConnectionResult.SUCCESS) {
                maybeMovePool(transactionInput2.getConnectedTransaction(), "kill");
                this.myUnspents.remove(transactionInput2.getConnectedOutput());
                log.info("Removing from UNSPENTS: {}", transactionInput2.getConnectedOutput());
            } else if (transactionInput2.connect(this.spent, TransactionInput.ConnectMode.DISCONNECT_ON_CONFLICT) == TransactionInput.ConnectionResult.SUCCESS) {
                maybeMovePool(transactionInput2.getConnectedTransaction(), "kill");
                this.myUnspents.remove(transactionInput2.getConnectedOutput());
                log.info("Removing from UNSPENTS: {}", transactionInput2.getConnectedOutput());
            }
        }
    }

    private void maybeMovePool(Transaction transaction, String str) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        if (transaction.isEveryOwnedOutputSpent(this)) {
            if (this.unspent.remove(transaction.getHash()) != null) {
                if (log.isInfoEnabled()) {
                    log.info("  {} {} <-unspent ->spent", transaction.getHashAsString(), str);
                }
                this.spent.put(transaction.getHash(), transaction);
                return;
            }
            return;
        }
        if (this.spent.remove(transaction.getHash()) != null) {
            if (log.isInfoEnabled()) {
                log.info("  {} {} <-spent ->unspent", transaction.getHashAsString(), str);
            }
            this.unspent.put(transaction.getHash(), transaction);
        }
    }

    public boolean maybeCommitTx(org.bitcoinj.core.Transaction r8) throws org.bitcoinj.core.VerificationException {
        throw new UnsupportedOperationException("Method not decompiled: org.bitcoinj.wallet.Wallet.maybeCommitTx(org.bitcoinj.core.Transaction):boolean");
    }

    public void commitTx(Transaction transaction) throws VerificationException {
        Preconditions.checkArgument(maybeCommitTx(transaction), "commitTx called on the same transaction twice");
    }

    public void addEventListener(WalletEventListener walletEventListener) {
        addChangeEventListener(Threading.USER_THREAD, walletEventListener);
        addCoinsReceivedEventListener(Threading.USER_THREAD, walletEventListener);
        addCoinsSentEventListener(Threading.USER_THREAD, walletEventListener);
        addKeyChainEventListener(Threading.USER_THREAD, walletEventListener);
        addReorganizeEventListener(Threading.USER_THREAD, walletEventListener);
        addScriptChangeEventListener(Threading.USER_THREAD, walletEventListener);
        addTransactionConfidenceEventListener(Threading.USER_THREAD, walletEventListener);
    }

    @Deprecated
    public void addEventListener(WalletEventListener walletEventListener, Executor executor) {
        addCoinsReceivedEventListener(executor, walletEventListener);
        addCoinsSentEventListener(executor, walletEventListener);
        addChangeEventListener(executor, walletEventListener);
        addKeyChainEventListener(executor, walletEventListener);
        addReorganizeEventListener(executor, walletEventListener);
        addScriptChangeEventListener(executor, walletEventListener);
        addTransactionConfidenceEventListener(executor, walletEventListener);
    }

    public void addChangeEventListener(WalletChangeEventListener walletChangeEventListener) {
        addChangeEventListener(Threading.USER_THREAD, walletChangeEventListener);
    }

    public void addChangeEventListener(Executor executor, WalletChangeEventListener walletChangeEventListener) {
        this.changeListeners.add(new ListenerRegistration<>(walletChangeEventListener, executor));
    }

    public void addCoinsReceivedEventListener(WalletCoinsReceivedEventListener walletCoinsReceivedEventListener) {
        addCoinsReceivedEventListener(Threading.USER_THREAD, walletCoinsReceivedEventListener);
    }

    public void addCoinsReceivedEventListener(Executor executor, WalletCoinsReceivedEventListener walletCoinsReceivedEventListener) {
        this.coinsReceivedListeners.add(new ListenerRegistration<>(walletCoinsReceivedEventListener, executor));
    }

    public void addCoinsSentEventListener(WalletCoinsSentEventListener walletCoinsSentEventListener) {
        addCoinsSentEventListener(Threading.USER_THREAD, walletCoinsSentEventListener);
    }

    public void addCoinsSentEventListener(Executor executor, WalletCoinsSentEventListener walletCoinsSentEventListener) {
        this.coinsSentListeners.add(new ListenerRegistration<>(walletCoinsSentEventListener, executor));
    }

    public void addKeyChainEventListener(KeyChainEventListener keyChainEventListener) {
        this.keyChainGroup.addEventListener(keyChainEventListener, Threading.USER_THREAD);
    }

    public void addKeyChainEventListener(Executor executor, KeyChainEventListener keyChainEventListener) {
        this.keyChainGroup.addEventListener(keyChainEventListener, executor);
    }

    public void addReorganizeEventListener(WalletReorganizeEventListener walletReorganizeEventListener) {
        addReorganizeEventListener(Threading.USER_THREAD, walletReorganizeEventListener);
    }

    public void addReorganizeEventListener(Executor executor, WalletReorganizeEventListener walletReorganizeEventListener) {
        this.reorganizeListeners.add(new ListenerRegistration<>(walletReorganizeEventListener, executor));
    }

    public void addScriptsChangeEventListener(ScriptsChangeEventListener scriptsChangeEventListener) {
        addScriptChangeEventListener(Threading.USER_THREAD, scriptsChangeEventListener);
    }

    public void addScriptChangeEventListener(Executor executor, ScriptsChangeEventListener scriptsChangeEventListener) {
        this.scriptChangeListeners.add(new ListenerRegistration<>(scriptsChangeEventListener, executor));
    }

    public void addTransactionConfidenceEventListener(TransactionConfidenceEventListener transactionConfidenceEventListener) {
        addTransactionConfidenceEventListener(Threading.USER_THREAD, transactionConfidenceEventListener);
    }

    public void addTransactionConfidenceEventListener(Executor executor, TransactionConfidenceEventListener transactionConfidenceEventListener) {
        this.transactionConfidenceListeners.add(new ListenerRegistration<>(transactionConfidenceEventListener, executor));
    }

    @Deprecated
    public boolean removeEventListener(WalletEventListener walletEventListener) {
        return removeChangeEventListener(walletEventListener) || removeCoinsReceivedEventListener(walletEventListener) || removeCoinsSentEventListener(walletEventListener) || removeKeyChainEventListener(walletEventListener) || removeReorganizeEventListener(walletEventListener) || removeTransactionConfidenceEventListener(walletEventListener);
    }

    public boolean removeChangeEventListener(WalletChangeEventListener walletChangeEventListener) {
        return ListenerRegistration.removeFromList(walletChangeEventListener, this.changeListeners);
    }

    public boolean removeCoinsReceivedEventListener(WalletCoinsReceivedEventListener walletCoinsReceivedEventListener) {
        return ListenerRegistration.removeFromList(walletCoinsReceivedEventListener, this.coinsReceivedListeners);
    }

    public boolean removeCoinsSentEventListener(WalletCoinsSentEventListener walletCoinsSentEventListener) {
        return ListenerRegistration.removeFromList(walletCoinsSentEventListener, this.coinsSentListeners);
    }

    public boolean removeKeyChainEventListener(KeyChainEventListener keyChainEventListener) {
        return this.keyChainGroup.removeEventListener(keyChainEventListener);
    }

    public boolean removeReorganizeEventListener(WalletReorganizeEventListener walletReorganizeEventListener) {
        return ListenerRegistration.removeFromList(walletReorganizeEventListener, this.reorganizeListeners);
    }

    public boolean removeScriptChangeEventListener(ScriptsChangeEventListener scriptsChangeEventListener) {
        return ListenerRegistration.removeFromList(scriptsChangeEventListener, this.scriptChangeListeners);
    }

    public boolean removeTransactionConfidenceEventListener(TransactionConfidenceEventListener transactionConfidenceEventListener) {
        return ListenerRegistration.removeFromList(transactionConfidenceEventListener, this.transactionConfidenceListeners);
    }

    public void queueOnTransactionConfidenceChanged(final Transaction transaction) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Iterator<ListenerRegistration<TransactionConfidenceEventListener>> it = this.transactionConfidenceListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<TransactionConfidenceEventListener> next = it.next();
            if (next.executor == Threading.SAME_THREAD) {
                next.listener.onTransactionConfidenceChanged(this, transaction);
            } else {
                next.executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        ((TransactionConfidenceEventListener) next.listener).onTransactionConfidenceChanged(Wallet.this, transaction);
                    }
                });
            }
        }
    }

    protected void maybeQueueOnWalletChanged() {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Preconditions.checkState(this.onWalletChangedSuppressions >= 0);
        if (this.onWalletChangedSuppressions > 0) {
            return;
        }
        Iterator<ListenerRegistration<WalletChangeEventListener>> it = this.changeListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<WalletChangeEventListener> next = it.next();
            next.executor.execute(new Runnable() {
                @Override
                public void run() {
                    ((WalletChangeEventListener) next.listener).onWalletChanged(Wallet.this);
                }
            });
        }
    }

    protected void queueOnCoinsReceived(final Transaction transaction, final Coin coin, final Coin coin2) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Iterator<ListenerRegistration<WalletCoinsReceivedEventListener>> it = this.coinsReceivedListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<WalletCoinsReceivedEventListener> next = it.next();
            next.executor.execute(new Runnable() {
                @Override
                public void run() {
                    ((WalletCoinsReceivedEventListener) next.listener).onCoinsReceived(Wallet.this, transaction, coin, coin2);
                }
            });
        }
    }

    protected void queueOnCoinsSent(final Transaction transaction, final Coin coin, final Coin coin2) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Iterator<ListenerRegistration<WalletCoinsSentEventListener>> it = this.coinsSentListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<WalletCoinsSentEventListener> next = it.next();
            next.executor.execute(new Runnable() {
                @Override
                public void run() {
                    ((WalletCoinsSentEventListener) next.listener).onCoinsSent(Wallet.this, transaction, coin, coin2);
                }
            });
        }
    }

    protected void queueOnReorganize() {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Preconditions.checkState(this.insideReorg);
        Iterator<ListenerRegistration<WalletReorganizeEventListener>> it = this.reorganizeListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<WalletReorganizeEventListener> next = it.next();
            next.executor.execute(new Runnable() {
                @Override
                public void run() {
                    ((WalletReorganizeEventListener) next.listener).onReorganize(Wallet.this);
                }
            });
        }
    }

    protected void queueOnScriptsChanged(final List<Script> list, final boolean z) {
        Iterator<ListenerRegistration<ScriptsChangeEventListener>> it = this.scriptChangeListeners.iterator();
        while (it.hasNext()) {
            final ListenerRegistration<ScriptsChangeEventListener> next = it.next();
            next.executor.execute(new Runnable() {
                @Override
                public void run() {
                    ((ScriptsChangeEventListener) next.listener).onScriptsChanged(Wallet.this, list, z);
                }
            });
        }
    }

    public Set<Transaction> getTransactions(boolean z) {
        this.lock.lock();
        try {
            HashSet hashSet = new HashSet();
            hashSet.addAll(this.unspent.values());
            hashSet.addAll(this.spent.values());
            hashSet.addAll(this.pending.values());
            if (z) {
                hashSet.addAll(this.dead.values());
            }
            return hashSet;
        } finally {
            this.lock.unlock();
        }
    }

    public Iterable<WalletTransaction> getWalletTransactions() {
        this.lock.lock();
        try {
            HashSet hashSet = new HashSet();
            addWalletTransactionsToSet(hashSet, WalletTransaction.Pool.UNSPENT, this.unspent.values());
            addWalletTransactionsToSet(hashSet, WalletTransaction.Pool.SPENT, this.spent.values());
            addWalletTransactionsToSet(hashSet, WalletTransaction.Pool.DEAD, this.dead.values());
            addWalletTransactionsToSet(hashSet, WalletTransaction.Pool.PENDING, this.pending.values());
            return hashSet;
        } finally {
            this.lock.unlock();
        }
    }

    private static void addWalletTransactionsToSet(Set<WalletTransaction> set, WalletTransaction.Pool pool, Collection<Transaction> collection) {
        Iterator<Transaction> it = collection.iterator();
        while (it.hasNext()) {
            set.add(new WalletTransaction(pool, it.next()));
        }
    }

    public void addWalletTransaction(WalletTransaction walletTransaction) {
        this.lock.lock();
        try {
            addWalletTransaction(walletTransaction.getPool(), walletTransaction.getTransaction());
        } finally {
            this.lock.unlock();
        }
    }

    private void addWalletTransaction(WalletTransaction.Pool pool, Transaction transaction) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        this.transactions.put(transaction.getHash(), transaction);
        int i = AnonymousClass11.$SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[pool.ordinal()];
        if (i == 1) {
            Preconditions.checkState(this.unspent.put(transaction.getHash(), transaction) == null);
        } else if (i == 2) {
            Preconditions.checkState(this.spent.put(transaction.getHash(), transaction) == null);
        } else if (i == 3) {
            Preconditions.checkState(this.pending.put(transaction.getHash(), transaction) == null);
        } else if (i == 4) {
            Preconditions.checkState(this.dead.put(transaction.getHash(), transaction) == null);
        } else {
            throw new RuntimeException("Unknown wallet transaction type " + pool);
        }
        if (pool == WalletTransaction.Pool.UNSPENT || pool == WalletTransaction.Pool.PENDING) {
            for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                if (transactionOutput.isAvailableForSpending() && transactionOutput.isMineOrWatched(this)) {
                    this.myUnspents.add(transactionOutput);
                }
            }
        }
        transaction.getConfidence().addEventListener(Threading.SAME_THREAD, this.txConfidenceListener);
    }

    static class AnonymousClass11 {
        static final int[] $SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool;

        static {
            int[] iArr = new int[WalletTransaction.Pool.values().length];
            $SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool = iArr;
            try {
                iArr[WalletTransaction.Pool.UNSPENT.ordinal()] = 1;
            } catch (NoSuchFieldError unused) {
            }
            try {
                $SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[WalletTransaction.Pool.SPENT.ordinal()] = 2;
            } catch (NoSuchFieldError unused2) {
            }
            try {
                $SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[WalletTransaction.Pool.PENDING.ordinal()] = 3;
            } catch (NoSuchFieldError unused3) {
            }
            try {
                $SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[WalletTransaction.Pool.DEAD.ordinal()] = 4;
            } catch (NoSuchFieldError unused4) {
            }
        }
    }

    public List<Transaction> getTransactionsByTime() {
        return getRecentTransactions(0, false);
    }

    public List<Transaction> getRecentTransactions(int i, boolean z) {
        this.lock.lock();
        try {
            Preconditions.checkArgument(i >= 0);
            int size = this.unspent.size() + this.spent.size() + this.pending.size();
            if (i > size || i == 0) {
                i = size;
            }
            ArrayList arrayList = new ArrayList(getTransactions(z));
            Collections.sort(arrayList, Transaction.SORT_TX_BY_UPDATE_TIME);
            if (i != arrayList.size()) {
                arrayList.subList(i, arrayList.size()).clear();
            }
            return arrayList;
        } finally {
            this.lock.unlock();
        }
    }

    @Nullable
    public Transaction getTransaction(Sha256Hash sha256Hash) {
        this.lock.lock();
        try {
            return this.transactions.get(sha256Hash);
        } finally {
            this.lock.unlock();
        }
    }

    @Override
    public Map<Sha256Hash, Transaction> getTransactionPool(WalletTransaction.Pool pool) {
        Map<Sha256Hash, Transaction> map;
        this.lock.lock();
        try {
            int i = AnonymousClass11.$SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[pool.ordinal()];
            if (i == 1) {
                map = this.unspent;
            } else if (i == 2) {
                map = this.spent;
            } else if (i == 3) {
                map = this.pending;
            } else if (i == 4) {
                map = this.dead;
            } else {
                throw new RuntimeException("Unknown wallet transaction type " + pool);
            }
            return map;
        } finally {
            this.lock.unlock();
        }
    }

    public void reset() {
        this.lock.lock();
        try {
            clearTransactions();
            this.lastBlockSeenHash = null;
            this.lastBlockSeenHeight = -1;
            this.lastBlockSeenTimeSecs = 0L;
            saveLater();
            maybeQueueOnWalletChanged();
        } finally {
            this.lock.unlock();
        }
    }

    public void clearTransactions(int i) {
        this.lock.lock();
        try {
            if (i == 0) {
                clearTransactions();
                saveLater();
                return;
            }
            throw new UnsupportedOperationException();
        } finally {
            this.lock.unlock();
        }
    }

    private void clearTransactions() {
        this.unspent.clear();
        this.spent.clear();
        this.pending.clear();
        this.dead.clear();
        this.transactions.clear();
        this.myUnspents.clear();
    }

    public List<TransactionOutput> getWatchedOutputs(boolean z) {
        this.lock.lock();
        this.keyChainGroupLock.lock();
        try {
            LinkedList newLinkedList = Lists.newLinkedList();
            for (Transaction transaction : Iterables.concat(this.unspent.values(), this.pending.values())) {
                if (!z || transaction.isMature()) {
                    for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                        if (transactionOutput.isAvailableForSpending()) {
                            try {
                                if (this.watchedScripts.contains(transactionOutput.getScriptPubKey())) {
                                    newLinkedList.add(transactionOutput);
                                }
                            } catch (ScriptException unused) {
                            }
                        }
                    }
                }
            }
            return newLinkedList;
        } finally {
            this.keyChainGroupLock.unlock();
            this.lock.unlock();
        }
    }

    public void cleanup() {
        this.lock.lock();
        boolean z = false;
        try {
            Iterator<Transaction> it = this.pending.values().iterator();
            while (it.hasNext()) {
                Transaction next = it.next();
                if (isTransactionRisky(next, null) && !this.acceptRiskyTransactions) {
                    log.debug("Found risky transaction {} in wallet during cleanup.", next.getHashAsString());
                    if (!next.isAnyOutputSpent()) {
                        for (TransactionInput transactionInput : next.getInputs()) {
                            TransactionOutput connectedOutput = transactionInput.getConnectedOutput();
                            if (connectedOutput != null) {
                                if (connectedOutput.isMineOrWatched(this)) {
                                    Preconditions.checkState(this.myUnspents.add(connectedOutput));
                                }
                                transactionInput.disconnect();
                            }
                        }
                        Iterator<TransactionOutput> it2 = next.getOutputs().iterator();
                        while (it2.hasNext()) {
                            this.myUnspents.remove(it2.next());
                        }
                        it.remove();
                        this.transactions.remove(next.getHash());
                        z = true;
                        log.info("Removed transaction {} from pending pool during cleanup.", next.getHashAsString());
                    } else {
                        log.info("Cannot remove transaction {} from pending pool during cleanup, as it's already spent partially.", next.getHashAsString());
                    }
                }
            }
            if (z) {
                isConsistentOrThrow();
                saveLater();
                if (log.isInfoEnabled()) {
                    log.info("Estimated balance is now: {}", getBalance(BalanceType.ESTIMATED).toFriendlyString());
                }
            }
        } finally {
            this.lock.unlock();
        }
    }

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

    public int getPoolSize(WalletTransaction.Pool pool) {
        int size;
        this.lock.lock();
        try {
            int i = AnonymousClass11.$SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[pool.ordinal()];
            if (i == 1) {
                size = this.unspent.size();
            } else if (i == 2) {
                size = this.spent.size();
            } else if (i == 3) {
                size = this.pending.size();
            } else if (i == 4) {
                size = this.dead.size();
            } else {
                throw new RuntimeException("Unreachable");
            }
            return size;
        } finally {
            this.lock.unlock();
        }
    }

    public boolean poolContainsTxHash(WalletTransaction.Pool pool, Sha256Hash sha256Hash) {
        boolean containsKey;
        this.lock.lock();
        try {
            int i = AnonymousClass11.$SwitchMap$org$bitcoinj$wallet$WalletTransaction$Pool[pool.ordinal()];
            if (i == 1) {
                containsKey = this.unspent.containsKey(sha256Hash);
            } else if (i == 2) {
                containsKey = this.spent.containsKey(sha256Hash);
            } else if (i == 3) {
                containsKey = this.pending.containsKey(sha256Hash);
            } else if (i == 4) {
                containsKey = this.dead.containsKey(sha256Hash);
            } else {
                throw new RuntimeException("Unreachable");
            }
            return containsKey;
        } finally {
            this.lock.unlock();
        }
    }

    public List<TransactionOutput> getUnspents() {
        this.lock.lock();
        try {
            return new ArrayList(this.myUnspents);
        } finally {
            this.lock.unlock();
        }
    }

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

    public String toString(boolean z, boolean z2, boolean z3, @Nullable AbstractBlockChain abstractBlockChain) {
        this.lock.lock();
        this.keyChainGroupLock.lock();
        try {
            StringBuilder sb = new StringBuilder();
            Coin balance = getBalance(BalanceType.ESTIMATED);
            Coin balance2 = getBalance(BalanceType.AVAILABLE_SPENDABLE);
            sb.append("Wallet containing ");
            sb.append(balance.toFriendlyString());
            sb.append(" (spendable: ");
            sb.append(balance2.toFriendlyString());
            sb.append(") in:\n");
            sb.append("  ");
            sb.append(this.pending.size());
            sb.append(" pending transactions\n");
            sb.append("  ");
            sb.append(this.unspent.size());
            sb.append(" unspent transactions\n");
            sb.append("  ");
            sb.append(this.spent.size());
            sb.append(" spent transactions\n");
            sb.append("  ");
            sb.append(this.dead.size());
            sb.append(" dead transactions\n");
            Date lastBlockSeenTime = getLastBlockSeenTime();
            sb.append("Last seen best block: ");
            sb.append(getLastBlockSeenHeight());
            sb.append(" (");
            sb.append(lastBlockSeenTime == null ? "time unknown" : Utils.dateTimeFormat(lastBlockSeenTime));
            sb.append("): ");
            sb.append(getLastBlockSeenHash());
            sb.append('\n');
            KeyCrypter keyCrypter = this.keyChainGroup.getKeyCrypter();
            if (keyCrypter != null) {
                sb.append("Encryption: ");
                sb.append(keyCrypter);
                sb.append('\n');
            }
            if (isWatching()) {
                sb.append("Wallet is watching.\n");
            }
            sb.append("\nKeys:\n");
            sb.append("Earliest creation time: ");
            sb.append(Utils.dateTimeFormat(getEarliestKeyCreationTime() * 1000));
            sb.append('\n');
            Date keyRotationTime = getKeyRotationTime();
            if (keyRotationTime != null) {
                sb.append("Key rotation time:      ");
                sb.append(Utils.dateTimeFormat(keyRotationTime));
                sb.append('\n');
            }
            sb.append(this.keyChainGroup.toString(z));
            if (!this.watchedScripts.isEmpty()) {
                sb.append("\nWatched scripts:\n");
                for (Script script : this.watchedScripts) {
                    sb.append("  ");
                    sb.append(script);
                    sb.append("\n");
                }
            }
            if (z2) {
                if (this.pending.size() > 0) {
                    sb.append("\n>>> PENDING:\n");
                    toStringHelper(sb, this.pending, abstractBlockChain, Transaction.SORT_TX_BY_UPDATE_TIME);
                }
                if (this.unspent.size() > 0) {
                    sb.append("\n>>> UNSPENT:\n");
                    toStringHelper(sb, this.unspent, abstractBlockChain, Transaction.SORT_TX_BY_HEIGHT);
                }
                if (this.spent.size() > 0) {
                    sb.append("\n>>> SPENT:\n");
                    toStringHelper(sb, this.spent, abstractBlockChain, Transaction.SORT_TX_BY_HEIGHT);
                }
                if (this.dead.size() > 0) {
                    sb.append("\n>>> DEAD:\n");
                    toStringHelper(sb, this.dead, abstractBlockChain, Transaction.SORT_TX_BY_UPDATE_TIME);
                }
            }
            if (z3 && this.extensions.size() > 0) {
                sb.append("\n>>> EXTENSIONS:\n");
                Iterator<WalletExtension> it = this.extensions.values().iterator();
                while (it.hasNext()) {
                    sb.append(it.next());
                    sb.append("\n\n");
                }
            }
            return sb.toString();
        } finally {
            this.keyChainGroupLock.unlock();
            this.lock.unlock();
        }
    }

    private void toStringHelper(StringBuilder sb, Map<Sha256Hash, Transaction> map, @Nullable AbstractBlockChain abstractBlockChain, @Nullable Comparator<Transaction> comparator) {
        Collection<Transaction> values;
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        if (comparator != null) {
            values = new TreeSet<>(comparator);
            values.addAll(map.values());
        } else {
            values = map.values();
        }
        for (Transaction transaction : values) {
            try {
                sb.append(transaction.getValue(this).toFriendlyString());
                sb.append(" total value (sends ");
                sb.append(transaction.getValueSentFromMe(this).toFriendlyString());
                sb.append(" and receives ");
                sb.append(transaction.getValueSentToMe(this).toFriendlyString());
                sb.append(")\n");
            } catch (ScriptException unused) {
            }
            if (transaction.hasConfidence()) {
                sb.append("  confidence: ");
                sb.append(transaction.getConfidence());
                sb.append('\n');
            }
            sb.append(transaction.toString(abstractBlockChain));
        }
    }

    public Collection<Transaction> getPendingTransactions() {
        this.lock.lock();
        try {
            return Collections.unmodifiableCollection(this.pending.values());
        } finally {
            this.lock.unlock();
        }
    }

    @Override
    public long getEarliestKeyCreationTime() {
        this.keyChainGroupLock.lock();
        try {
            long earliestKeyCreationTime = this.keyChainGroup.getEarliestKeyCreationTime();
            Iterator<Script> it = this.watchedScripts.iterator();
            while (it.hasNext()) {
                earliestKeyCreationTime = Math.min(it.next().getCreationTimeSeconds(), earliestKeyCreationTime);
            }
            if (earliestKeyCreationTime == LongCompanionObject.MAX_VALUE) {
                earliestKeyCreationTime = Utils.currentTimeSeconds();
            }
            return earliestKeyCreationTime;
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    @Nullable
    public Sha256Hash getLastBlockSeenHash() {
        this.lock.lock();
        try {
            return this.lastBlockSeenHash;
        } finally {
            this.lock.unlock();
        }
    }

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

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

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

    public long getLastBlockSeenTimeSecs() {
        this.lock.lock();
        try {
            return this.lastBlockSeenTimeSecs;
        } finally {
            this.lock.unlock();
        }
    }

    @Nullable
    public Date getLastBlockSeenTime() {
        long lastBlockSeenTimeSecs = getLastBlockSeenTimeSecs();
        if (lastBlockSeenTimeSecs == 0) {
            return null;
        }
        return new Date(lastBlockSeenTimeSecs * 1000);
    }

    public int getLastBlockSeenHeight() {
        this.lock.lock();
        try {
            return this.lastBlockSeenHeight;
        } finally {
            this.lock.unlock();
        }
    }

    public int getVersion() {
        return this.version;
    }

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

    public void setDescription(String str) {
        this.description = str;
    }

    public String getDescription() {
        return this.description;
    }

    @Deprecated
    public Coin getWatchedBalance() {
        return getBalance();
    }

    @Deprecated
    public Coin getWatchedBalance(CoinSelector coinSelector) {
        return getBalance(coinSelector);
    }

    public Coin getBalance() {
        return getBalance(BalanceType.AVAILABLE);
    }

    public Coin getBalance(BalanceType balanceType) {
        this.lock.lock();
        try {
            boolean z = true;
            if (balanceType != BalanceType.AVAILABLE && balanceType != BalanceType.AVAILABLE_SPENDABLE) {
                if (balanceType != BalanceType.ESTIMATED && balanceType != BalanceType.ESTIMATED_SPENDABLE) {
                    throw new AssertionError("Unknown balance type");
                }
                if (balanceType != BalanceType.ESTIMATED_SPENDABLE) {
                    z = false;
                }
                List<TransactionOutput> calculateAllSpendCandidates = calculateAllSpendCandidates(false, z);
                Coin coin = Coin.ZERO;
                Iterator<TransactionOutput> it = calculateAllSpendCandidates.iterator();
                while (it.hasNext()) {
                    coin = coin.add(it.next().getValue());
                }
                return coin;
            }
            return this.coinSelector.select(NetworkParameters.MAX_MONEY, calculateAllSpendCandidates(true, balanceType == BalanceType.AVAILABLE_SPENDABLE)).valueGathered;
        } finally {
            this.lock.unlock();
        }
    }

    public Coin getBalance(CoinSelector coinSelector) {
        this.lock.lock();
        try {
            Preconditions.checkNotNull(coinSelector);
            return coinSelector.select(this.params.getMaxMoney(), calculateAllSpendCandidates(true, false)).valueGathered;
        } finally {
            this.lock.unlock();
        }
    }

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

        private BalanceFutureRequest() {
        }
    }

    public ListenableFuture<Coin> getBalanceFuture(Coin coin, BalanceType balanceType) {
        this.lock.lock();
        try {
            SettableFuture<Coin> create = SettableFuture.create();
            Coin balance = getBalance(balanceType);
            if (balance.compareTo(coin) >= 0) {
                create.set(balance);
            } else {
                BalanceFutureRequest balanceFutureRequest = new BalanceFutureRequest();
                balanceFutureRequest.future = create;
                balanceFutureRequest.value = coin;
                balanceFutureRequest.type = balanceType;
                this.balanceFutureRequests.add(balanceFutureRequest);
            }
            return create;
        } finally {
            this.lock.unlock();
        }
    }

    public void checkBalanceFuturesLocked(@Nullable Coin coin) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        ListIterator<BalanceFutureRequest> listIterator = this.balanceFutureRequests.listIterator();
        while (listIterator.hasNext()) {
            final BalanceFutureRequest next = listIterator.next();
            final Coin balance = getBalance(next.type);
            if (balance.compareTo(next.value) >= 0) {
                listIterator.remove();
                Threading.USER_THREAD.execute(new Runnable() {
                    @Override
                    public void run() {
                        next.future.set(balance);
                    }
                });
            }
        }
    }

    public Coin getTotalReceived() {
        Coin coin = Coin.ZERO;
        for (Transaction transaction : this.transactions.values()) {
            Coin coin2 = Coin.ZERO;
            for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                if (transactionOutput.isMine(this)) {
                    coin2 = coin2.add(transactionOutput.getValue());
                }
            }
            Iterator<TransactionInput> it = transaction.getInputs().iterator();
            while (it.hasNext()) {
                TransactionOutput connectedOutput = it.next().getConnectedOutput();
                if (connectedOutput != null && connectedOutput.isMine(this)) {
                    coin2 = coin2.subtract(connectedOutput.getValue());
                }
            }
            if (coin2.isPositive()) {
                coin = coin.add(coin2);
            }
        }
        return coin;
    }

    public Coin getTotalSent() {
        Coin coin = Coin.ZERO;
        for (Transaction transaction : this.transactions.values()) {
            Coin coin2 = Coin.ZERO;
            for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                if (!transactionOutput.isMine(this)) {
                    coin2 = coin2.add(transactionOutput.getValue());
                }
            }
            Coin coin3 = Coin.ZERO;
            Iterator<TransactionInput> it = transaction.getInputs().iterator();
            while (it.hasNext()) {
                TransactionOutput connectedOutput = it.next().getConnectedOutput();
                if (connectedOutput != null && connectedOutput.isMine(this)) {
                    coin3 = coin3.add(connectedOutput.getValue());
                }
            }
            Coin inputSum = transaction.getInputSum();
            if (coin3 != inputSum) {
                coin2 = Coin.valueOf(new BigInteger(coin2.toString()).multiply(new BigInteger(coin3.toString())).divide(new BigInteger(inputSum.toString())).longValue());
            }
            coin = coin.add(coin2);
        }
        return coin;
    }

    public Transaction createSend(Address address, Coin coin) throws InsufficientMoneyException {
        SendRequest sendRequest = SendRequest.to(address, coin);
        if (this.params.getId().equals(NetworkParameters.ID_UNITTESTNET)) {
            sendRequest.shuffleOutputs = false;
        }
        completeTx(sendRequest);
        return sendRequest.tx;
    }

    public Transaction sendCoinsOffline(SendRequest sendRequest) throws InsufficientMoneyException {
        this.lock.lock();
        try {
            completeTx(sendRequest);
            commitTx(sendRequest.tx);
            return sendRequest.tx;
        } finally {
            this.lock.unlock();
        }
    }

    public SendResult sendCoins(TransactionBroadcaster transactionBroadcaster, Address address, Coin coin) throws InsufficientMoneyException {
        return sendCoins(transactionBroadcaster, SendRequest.to(address, coin));
    }

    public SendResult sendCoins(TransactionBroadcaster transactionBroadcaster, SendRequest sendRequest) throws InsufficientMoneyException {
        Preconditions.checkState(!this.lock.isHeldByCurrentThread());
        Transaction sendCoinsOffline = sendCoinsOffline(sendRequest);
        SendResult sendResult = new SendResult();
        sendResult.tx = sendCoinsOffline;
        sendResult.broadcast = transactionBroadcaster.broadcastTransaction(sendCoinsOffline);
        sendResult.broadcastComplete = sendResult.broadcast.future();
        return sendResult;
    }

    public SendResult sendCoins(SendRequest sendRequest) throws InsufficientMoneyException {
        TransactionBroadcaster transactionBroadcaster = this.vTransactionBroadcaster;
        Preconditions.checkState(transactionBroadcaster != null, "No transaction broadcaster is configured");
        return sendCoins(transactionBroadcaster, sendRequest);
    }

    public Transaction sendCoins(Peer peer, SendRequest sendRequest) throws InsufficientMoneyException {
        Transaction sendCoinsOffline = sendCoinsOffline(sendRequest);
        peer.sendMessage(sendCoinsOffline);
        return sendCoinsOffline;
    }

    public void completeTx(SendRequest sendRequest) throws InsufficientMoneyException {
        CoinSelection select;
        this.lock.lock();
        try {
            Preconditions.checkArgument(!sendRequest.completed, "Given SendRequest has already been completed.");
            Coin coin = Coin.ZERO;
            Iterator<TransactionOutput> it = sendRequest.tx.getOutputs().iterator();
            while (it.hasNext()) {
                coin = coin.add(it.next().getValue());
            }
            log.info("Completing send tx with {} outputs totalling {} and a fee of {}/kB", new Object[]{Integer.valueOf(sendRequest.tx.getOutputs().size()), coin.toFriendlyString(), sendRequest.feePerKb.toFriendlyString()});
            Coin coin2 = Coin.ZERO;
            for (TransactionInput transactionInput : sendRequest.tx.getInputs()) {
                if (transactionInput.getConnectedOutput() != null) {
                    coin2 = coin2.add(transactionInput.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.");
                }
            }
            Coin subtract = coin.subtract(coin2);
            ArrayList arrayList = new ArrayList(sendRequest.tx.getInputs());
            if (sendRequest.ensureMinRequiredFee && !sendRequest.emptyWallet) {
                int i = 0;
                for (TransactionOutput transactionOutput : sendRequest.tx.getOutputs()) {
                    if (transactionOutput.isDust()) {
                        throw new DustySendRequested();
                    }
                    if (transactionOutput.getScriptPubKey().isOpReturn()) {
                        i++;
                    }
                }
                if (i > 1) {
                    throw new MultipleOpReturnRequested();
                }
            }
            List<TransactionOutput> calculateAllSpendCandidates = calculateAllSpendCandidates(true, sendRequest.missingSigsMode == MissingSigsMode.THROW);
            TransactionOutput transactionOutput2 = null;
            if (!sendRequest.emptyWallet) {
                FeeCalculation calculateFee = calculateFee(sendRequest, subtract, arrayList, sendRequest.ensureMinRequiredFee, calculateAllSpendCandidates);
                select = calculateFee.bestCoinSelection;
                transactionOutput2 = calculateFee.bestChangeOutput;
            } else {
                Preconditions.checkState(sendRequest.tx.getOutputs().size() == 1, "Empty wallet TX must have a single output only.");
                select = (sendRequest.coinSelector == null ? this.coinSelector : sendRequest.coinSelector).select(this.params.getMaxMoney(), calculateAllSpendCandidates);
                sendRequest.tx.getOutput(0L).setValue(select.valueGathered);
                log.info("  emptying {}", select.valueGathered.toFriendlyString());
            }
            Iterator<TransactionOutput> it2 = select.gathered.iterator();
            while (it2.hasNext()) {
                sendRequest.tx.addInput(it2.next());
            }
            if (sendRequest.emptyWallet) {
                if (!adjustOutputDownwardsForFee(sendRequest.tx, select, sendRequest.feePerKb == null ? Coin.ZERO : sendRequest.feePerKb, sendRequest.ensureMinRequiredFee)) {
                    throw new CouldNotAdjustDownwards();
                }
            }
            if (transactionOutput2 != null) {
                sendRequest.tx.addOutput(transactionOutput2);
                log.info("  with {} change", transactionOutput2.getValue().toFriendlyString());
            }
            if (sendRequest.shuffleOutputs) {
                sendRequest.tx.shuffleOutputs();
            }
            if (sendRequest.signInputs) {
                signTransaction(sendRequest);
            }
            if (sendRequest.tx.unsafeBitcoinSerialize().length > 100000) {
                throw new ExceededMaxTransactionSize();
            }
            sendRequest.tx.getConfidence().setSource(TransactionConfidence.Source.SELF);
            sendRequest.tx.setPurpose(Transaction.Purpose.USER_PAYMENT);
            sendRequest.tx.setExchangeRate(sendRequest.exchangeRate);
            sendRequest.tx.setMemo(sendRequest.memo);
            sendRequest.completed = true;
            log.info("  completed: {}", sendRequest.tx);
        } finally {
            this.lock.unlock();
        }
    }

    public void signTransaction(SendRequest sendRequest) {
        this.lock.lock();
        try {
            Transaction transaction = sendRequest.tx;
            List<TransactionInput> inputs = transaction.getInputs();
            List<TransactionOutput> outputs = transaction.getOutputs();
            Preconditions.checkState(inputs.size() > 0);
            Preconditions.checkState(outputs.size() > 0);
            DecryptingKeyBag decryptingKeyBag = new DecryptingKeyBag(this, sendRequest.aesKey);
            int size = transaction.getInputs().size();
            for (int i = 0; i < size; i++) {
                long j = i;
                TransactionInput input = transaction.getInput(j);
                if (input.getConnectedOutput() != null) {
                    try {
                        input.getScriptSig().correctlySpends(transaction, j, input.getConnectedOutput().getScriptPubKey());
                        log.warn("Input {} already correctly spends output, assuming SIGHASH type used will be safe and skipping signing.", Integer.valueOf(i));
                    } catch (ScriptException e) {
                        log.debug("Input contained an incorrect signature", e);
                        Script scriptPubKey = input.getConnectedOutput().getScriptPubKey();
                        RedeemData connectedRedeemData = input.getConnectedRedeemData(decryptingKeyBag);
                        Preconditions.checkNotNull(connectedRedeemData, "Transaction exists in wallet that we cannot redeem: %s", new Object[]{input.getOutpoint().getHash()});
                        input.setScriptSig(scriptPubKey.createEmptyInputScript(connectedRedeemData.keys.get(0), connectedRedeemData.redeemScript));
                    }
                }
            }
            TransactionSigner.ProposedTransaction proposedTransaction = new TransactionSigner.ProposedTransaction(transaction);
            for (TransactionSigner transactionSigner : this.signers) {
                if (!transactionSigner.signInputs(proposedTransaction, decryptingKeyBag)) {
                    log.info("{} returned false for the tx", transactionSigner.getClass().getName());
                }
            }
            new MissingSigResolutionSigner(sendRequest.missingSigsMode).signInputs(proposedTransaction, decryptingKeyBag);
        } finally {
            this.lock.unlock();
        }
    }

    private boolean adjustOutputDownwardsForFee(Transaction transaction, CoinSelection coinSelection, Coin coin, boolean z) {
        Coin divide = coin.multiply(transaction.unsafeBitcoinSerialize().length + estimateBytesForSigning(coinSelection)).divide(1000L);
        if (z && divide.compareTo(Transaction.REFERENCE_DEFAULT_MIN_TX_FEE) < 0) {
            divide = Transaction.REFERENCE_DEFAULT_MIN_TX_FEE;
        }
        TransactionOutput output = transaction.getOutput(0L);
        output.setValue(output.getValue().subtract(divide));
        return !output.isDust();
    }

    public List<TransactionOutput> calculateAllSpendCandidates() {
        return calculateAllSpendCandidates(true, true);
    }

    @Deprecated
    public List<TransactionOutput> calculateAllSpendCandidates(boolean z) {
        return calculateAllSpendCandidates(z, true);
    }

    public List<TransactionOutput> calculateAllSpendCandidates(boolean z, boolean z2) {
        List<TransactionOutput> calculateAllSpendCandidatesFromUTXOProvider;
        this.lock.lock();
        try {
            if (this.vUTXOProvider == null) {
                calculateAllSpendCandidatesFromUTXOProvider = new ArrayList<>(this.myUnspents.size());
                Iterator<TransactionOutput> it = this.myUnspents.iterator();
                while (it.hasNext()) {
                    TransactionOutput next = it.next();
                    if (!z2 || canSignFor(next.getScriptPubKey())) {
                        Transaction transaction = (Transaction) Preconditions.checkNotNull(next.getParentTransaction());
                        if (!z || transaction.isMature()) {
                            calculateAllSpendCandidatesFromUTXOProvider.add(next);
                        }
                    }
                }
            } else {
                calculateAllSpendCandidatesFromUTXOProvider = calculateAllSpendCandidatesFromUTXOProvider(z);
            }
            return calculateAllSpendCandidatesFromUTXOProvider;
        } finally {
            this.lock.unlock();
        }
    }

    public boolean canSignFor(Script script) {
        if (script.isSentToRawPubKey()) {
            ECKey findKeyFromPubKey = findKeyFromPubKey(script.getPubKey());
            if (findKeyFromPubKey != null) {
                return findKeyFromPubKey.isEncrypted() || findKeyFromPubKey.hasPrivKey();
            }
            return false;
        }
        if (script.isPayToScriptHash()) {
            RedeemData findRedeemDataFromScriptHash = findRedeemDataFromScriptHash(script.getPubKeyHash());
            return findRedeemDataFromScriptHash != null && canSignFor(findRedeemDataFromScriptHash.redeemScript);
        }
        if (script.isSentToAddress()) {
            ECKey findKeyFromPubHash = findKeyFromPubHash(script.getPubKeyHash());
            if (findKeyFromPubHash != null) {
                return findKeyFromPubHash.isEncrypted() || findKeyFromPubHash.hasPrivKey();
            }
            return false;
        }
        if (script.isSentToMultiSig()) {
            Iterator<ECKey> it = script.getPubKeys().iterator();
            while (it.hasNext()) {
                ECKey findKeyFromPubKey2 = findKeyFromPubKey(it.next().getPubKey());
                if (findKeyFromPubKey2 != null && (findKeyFromPubKey2.isEncrypted() || findKeyFromPubKey2.hasPrivKey())) {
                    return true;
                }
            }
        } else if (script.isSentToCLTVPaymentChannel()) {
            byte[] cLTVPaymentChannelSenderPubKey = script.getCLTVPaymentChannelSenderPubKey();
            ECKey findKeyFromPubKey3 = findKeyFromPubKey(cLTVPaymentChannelSenderPubKey);
            if (findKeyFromPubKey3 != null && (findKeyFromPubKey3.isEncrypted() || findKeyFromPubKey3.hasPrivKey())) {
                return true;
            }
            script.getCLTVPaymentChannelRecipientPubKey();
            ECKey findKeyFromPubKey4 = findKeyFromPubKey(cLTVPaymentChannelSenderPubKey);
            if (findKeyFromPubKey4 != null && (findKeyFromPubKey4.isEncrypted() || findKeyFromPubKey4.hasPrivKey())) {
                return true;
            }
        }
        return false;
    }

    protected LinkedList<TransactionOutput> calculateAllSpendCandidatesFromUTXOProvider(boolean z) {
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        UTXOProvider uTXOProvider = (UTXOProvider) Preconditions.checkNotNull(this.vUTXOProvider, "No UTXO provider has been set");
        LinkedList<TransactionOutput> newLinkedList = Lists.newLinkedList();
        try {
            int chainHeadHeight = uTXOProvider.getChainHeadHeight();
            for (UTXO utxo : getStoredOutputsFromUTXOProvider()) {
                boolean isCoinbase = utxo.isCoinbase();
                int height = (chainHeadHeight - utxo.getHeight()) + 1;
                if (!z || !isCoinbase || height >= this.params.getSpendableCoinbaseDepth()) {
                    newLinkedList.add(new FreeStandingTransactionOutput(this.params, utxo, chainHeadHeight));
                }
            }
            for (Transaction transaction : this.pending.values()) {
                for (TransactionInput transactionInput : transaction.getInputs()) {
                    if (transactionInput.getConnectedOutput().isMine(this)) {
                        newLinkedList.remove(transactionInput.getConnectedOutput());
                    }
                }
                if (!z || transaction.isMature()) {
                    for (TransactionOutput transactionOutput : transaction.getOutputs()) {
                        if (transactionOutput.isAvailableForSpending() && transactionOutput.isMine(this)) {
                            newLinkedList.add(transactionOutput);
                        }
                    }
                }
            }
            return newLinkedList;
        } catch (UTXOProviderException e) {
            throw new RuntimeException("UTXO provider error", e);
        }
    }

    protected List<UTXO> getStoredOutputsFromUTXOProvider() throws UTXOProviderException {
        UTXOProvider uTXOProvider = (UTXOProvider) Preconditions.checkNotNull(this.vUTXOProvider, "No UTXO provider has been set");
        ArrayList arrayList = new ArrayList();
        List<ECKey> importedKeys = getImportedKeys();
        importedKeys.addAll(getActiveKeyChain().getLeafKeys());
        ArrayList arrayList2 = new ArrayList();
        Iterator<ECKey> it = importedKeys.iterator();
        while (it.hasNext()) {
            arrayList2.add(new Address(this.params, it.next().getPubKeyHash()));
        }
        arrayList.addAll(uTXOProvider.getOpenTransactionOutputs(arrayList2));
        return arrayList;
    }

    public CoinSelector getCoinSelector() {
        this.lock.lock();
        try {
            return this.coinSelector;
        } finally {
            this.lock.unlock();
        }
    }

    public void setCoinSelector(CoinSelector coinSelector) {
        this.lock.lock();
        try {
            this.coinSelector = (CoinSelector) Preconditions.checkNotNull(coinSelector);
        } finally {
            this.lock.unlock();
        }
    }

    public void allowSpendingUnconfirmedTransactions() {
        setCoinSelector(AllowUnconfirmedCoinSelector.get());
    }

    @Nullable
    public UTXOProvider getUTXOProvider() {
        this.lock.lock();
        try {
            return this.vUTXOProvider;
        } finally {
            this.lock.unlock();
        }
    }

    public void setUTXOProvider(@Nullable UTXOProvider uTXOProvider) {
        boolean z;
        this.lock.lock();
        if (uTXOProvider != null) {
            try {
                if (!uTXOProvider.getParams().equals(this.params)) {
                    z = false;
                    Preconditions.checkArgument(z);
                    this.vUTXOProvider = uTXOProvider;
                    this.lock.unlock();
                }
            } catch (Throwable th) {
                this.lock.unlock();
                throw th;
            }
        }
        z = true;
        Preconditions.checkArgument(z);
        this.vUTXOProvider = uTXOProvider;
        this.lock.unlock();
    }

    private class FreeStandingTransactionOutput extends TransactionOutput {
        private int chainHeight;
        private UTXO output;

        public FreeStandingTransactionOutput(NetworkParameters networkParameters, UTXO utxo, int i) {
            super(networkParameters, (Transaction) null, utxo.getValue(), utxo.getScript().getProgram());
            this.output = utxo;
            this.chainHeight = i;
        }

        public UTXO getUTXO() {
            return this.output;
        }

        @Override
        public int getParentTransactionDepthInBlocks() {
            return (this.chainHeight - this.output.getHeight()) + 1;
        }

        @Override
        public int getIndex() {
            return (int) this.output.getIndex();
        }

        @Override
        public Sha256Hash getParentTransactionHash() {
            return this.output.getHash();
        }
    }

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

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

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

    @Override
    public void reorganize(StoredBlock storedBlock, List<StoredBlock> list, List<StoredBlock> list2) throws VerificationException {
        this.lock.lock();
        try {
            Preconditions.checkState(this.confidenceChanged.size() == 0);
            Preconditions.checkState(!this.insideReorg);
            this.insideReorg = true;
            Preconditions.checkState(this.onWalletChangedSuppressions == 0);
            this.onWalletChangedSuppressions++;
            ArrayListMultimap create = ArrayListMultimap.create();
            for (Transaction transaction : getTransactions(true)) {
                Map<Sha256Hash, Integer> appearsInHashes = transaction.getAppearsInHashes();
                if (appearsInHashes != null) {
                    for (Map.Entry<Sha256Hash, Integer> entry : appearsInHashes.entrySet()) {
                        create.put(entry.getKey(), new TxOffsetPair(transaction, entry.getValue().intValue()));
                    }
                }
            }
            Iterator it = create.keySet().iterator();
            while (it.hasNext()) {
                Collections.sort(create.get((Sha256Hash) it.next()));
            }
            ArrayList arrayList = new ArrayList(list.size());
            log.info("Old part of chain (top to bottom):");
            for (StoredBlock storedBlock2 : list) {
                log.info("  {}", storedBlock2.getHeader().getHashAsString());
                arrayList.add(storedBlock2.getHeader().getHash());
            }
            log.info("New part of chain (top to bottom):");
            Iterator<StoredBlock> it2 = list2.iterator();
            while (it2.hasNext()) {
                log.info("  {}", it2.next().getHeader().getHashAsString());
            }
            Collections.reverse(list2);
            LinkedList newLinkedList = Lists.newLinkedList();
            Iterator it3 = arrayList.iterator();
            while (it3.hasNext()) {
                Iterator it4 = create.get((Sha256Hash) it3.next()).iterator();
                while (it4.hasNext()) {
                    Transaction transaction2 = ((TxOffsetPair) it4.next()).tx;
                    Sha256Hash hash = transaction2.getHash();
                    if (transaction2.isCoinBase()) {
                        log.warn("Coinbase killed by re-org: {}", transaction2.getHashAsString());
                        killTxns(ImmutableSet.of(transaction2), null);
                    } else {
                        for (TransactionOutput transactionOutput : transaction2.getOutputs()) {
                            TransactionInput spentBy = transactionOutput.getSpentBy();
                            if (spentBy != null) {
                                if (transactionOutput.isMineOrWatched(this)) {
                                    Preconditions.checkState(this.myUnspents.add(transactionOutput));
                                }
                                spentBy.disconnect();
                            }
                        }
                        newLinkedList.add(transaction2);
                        this.unspent.remove(hash);
                        this.spent.remove(hash);
                        Preconditions.checkState(!this.pending.containsKey(hash));
                        Preconditions.checkState(!this.dead.containsKey(hash));
                    }
                }
            }
            Iterator it5 = newLinkedList.iterator();
            while (it5.hasNext()) {
                Transaction transaction3 = (Transaction) it5.next();
                if (!transaction3.isCoinBase()) {
                    log.info("  ->pending {}", transaction3.getHash());
                    transaction3.getConfidence().setConfidenceType(TransactionConfidence.ConfidenceType.PENDING);
                    this.confidenceChanged.put(transaction3, TransactionConfidence.Listener.ChangeReason.TYPE);
                    addWalletTransaction(WalletTransaction.Pool.PENDING, transaction3);
                    updateForSpends(transaction3, false);
                }
            }
            int size = list.size();
            log.info("depthToSubtract = " + size);
            subtractDepth(size, this.spent.values());
            subtractDepth(size, this.unspent.values());
            subtractDepth(size, this.dead.values());
            setLastBlockSeenHash(storedBlock.getHeader().getHash());
            for (StoredBlock storedBlock3 : list2) {
                log.info("Replaying block {}", storedBlock3.getHeader().getHashAsString());
                for (TxOffsetPair txOffsetPair : create.get(storedBlock3.getHeader().getHash())) {
                    log.info("  tx {}", txOffsetPair.tx.getHash());
                    try {
                        receive(txOffsetPair.tx, storedBlock3, AbstractBlockChain.NewBlockType.BEST_CHAIN, txOffsetPair.offset);
                    } catch (ScriptException e) {
                        throw new RuntimeException(e);
                    }
                }
                notifyNewBestBlock(storedBlock3);
            }
            isConsistentOrThrow();
            Coin balance = getBalance();
            log.info("post-reorg balance is {}", balance.toFriendlyString());
            queueOnReorganize();
            this.insideReorg = false;
            this.onWalletChangedSuppressions--;
            maybeQueueOnWalletChanged();
            checkBalanceFuturesLocked(balance);
            informConfidenceListenersIfNotReorganizing();
            saveLater();
        } finally {
            this.lock.unlock();
        }
    }

    private void subtractDepth(int i, Collection<Transaction> collection) {
        for (Transaction transaction : collection) {
            if (transaction.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
                transaction.getConfidence().setDepthInBlocks(transaction.getConfidence().getDepthInBlocks() - i);
                this.confidenceChanged.put(transaction, TransactionConfidence.Listener.ChangeReason.DEPTH);
            }
        }
    }

    @Override
    public void beginBloomFilterCalculation() {
        if (this.bloomFilterGuard.incrementAndGet() > 1) {
            return;
        }
        this.lock.lock();
        this.keyChainGroupLock.lock();
        calcBloomOutPointsLocked();
    }

    private void calcBloomOutPointsLocked() {
        this.bloomOutPoints.clear();
        HashSet hashSet = new HashSet();
        hashSet.addAll(this.unspent.values());
        hashSet.addAll(this.spent.values());
        hashSet.addAll(this.pending.values());
        Iterator it = hashSet.iterator();
        while (it.hasNext()) {
            for (TransactionOutput transactionOutput : ((Transaction) it.next()).getOutputs()) {
                try {
                    if (isTxOutputBloomFilterable(transactionOutput)) {
                        this.bloomOutPoints.add(transactionOutput.getOutPointFor());
                    }
                } catch (ScriptException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    @Override
    @GuardedBy("keyChainGroupLock")
    public void endBloomFilterCalculation() {
        if (this.bloomFilterGuard.decrementAndGet() > 0) {
            return;
        }
        this.bloomOutPoints.clear();
        this.keyChainGroupLock.unlock();
        this.lock.unlock();
    }

    @Override
    public int getBloomFilterElementCount() {
        beginBloomFilterCalculation();
        try {
            return this.bloomOutPoints.size() + this.keyChainGroup.getBloomFilterElementCount() + this.watchedScripts.size();
        } finally {
            endBloomFilterCalculation();
        }
    }

    @Override
    public boolean isRequiringUpdateAllBloomFilter() {
        this.keyChainGroupLock.lock();
        try {
            return !this.watchedScripts.isEmpty();
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public BloomFilter getBloomFilter(double d) {
        beginBloomFilterCalculation();
        try {
            return getBloomFilter(getBloomFilterElementCount(), d, (long) (Math.random() * 9.223372036854776E18d));
        } finally {
            endBloomFilterCalculation();
        }
    }

    @Override
    @GuardedBy("keyChainGroupLock")
    public BloomFilter getBloomFilter(int i, double d, long j) {
        beginBloomFilterCalculation();
        try {
            BloomFilter bloomFilter = this.keyChainGroup.getBloomFilter(i, d, j);
            Iterator<Script> it = this.watchedScripts.iterator();
            while (it.hasNext()) {
                for (ScriptChunk scriptChunk : it.next().getChunks()) {
                    if (!scriptChunk.isOpCode() && scriptChunk.data.length >= 8) {
                        bloomFilter.insert(scriptChunk.data);
                    }
                }
            }
            Iterator<TransactionOutPoint> it2 = this.bloomOutPoints.iterator();
            while (it2.hasNext()) {
                bloomFilter.insert(it2.next().unsafeBitcoinSerialize());
            }
            return bloomFilter;
        } finally {
            endBloomFilterCalculation();
        }
    }

    private boolean isTxOutputBloomFilterable(TransactionOutput transactionOutput) {
        Script scriptPubKey = transactionOutput.getScriptPubKey();
        return ((scriptPubKey.isSentToRawPubKey() || scriptPubKey.isPayToScriptHash()) && this.myUnspents.contains(transactionOutput)) || this.watchedScripts.contains(scriptPubKey);
    }

    public boolean checkForFilterExhaustion(FilteredBlock filteredBlock) {
        this.keyChainGroupLock.lock();
        try {
            int combinedKeyLookaheadEpochs = this.keyChainGroup.getCombinedKeyLookaheadEpochs();
            Iterator<Transaction> it = filteredBlock.getAssociatedTransactions().values().iterator();
            while (it.hasNext()) {
                markKeysAsUsed(it.next());
            }
            int combinedKeyLookaheadEpochs2 = this.keyChainGroup.getCombinedKeyLookaheadEpochs();
            Preconditions.checkState(combinedKeyLookaheadEpochs2 >= combinedKeyLookaheadEpochs);
            return combinedKeyLookaheadEpochs2 > combinedKeyLookaheadEpochs;
        } finally {
            this.keyChainGroupLock.unlock();
        }
    }

    public void addExtension(WalletExtension walletExtension) {
        String walletExtensionID = ((WalletExtension) Preconditions.checkNotNull(walletExtension)).getWalletExtensionID();
        this.lock.lock();
        try {
            if (this.extensions.containsKey(walletExtensionID)) {
                throw new IllegalStateException("Cannot add two extensions with the same ID: " + walletExtensionID);
            }
            this.extensions.put(walletExtensionID, walletExtension);
            saveNow();
        } finally {
            this.lock.unlock();
        }
    }

    public WalletExtension addOrGetExistingExtension(WalletExtension walletExtension) {
        String walletExtensionID = ((WalletExtension) Preconditions.checkNotNull(walletExtension)).getWalletExtensionID();
        this.lock.lock();
        try {
            WalletExtension walletExtension2 = this.extensions.get(walletExtensionID);
            if (walletExtension2 != null) {
                return walletExtension2;
            }
            this.extensions.put(walletExtensionID, walletExtension);
            saveNow();
            return walletExtension;
        } finally {
            this.lock.unlock();
        }
    }

    public void addOrUpdateExtension(WalletExtension walletExtension) {
        String walletExtensionID = ((WalletExtension) Preconditions.checkNotNull(walletExtension)).getWalletExtensionID();
        this.lock.lock();
        try {
            this.extensions.put(walletExtensionID, walletExtension);
            saveNow();
        } finally {
            this.lock.unlock();
        }
    }

    public Map<String, WalletExtension> getExtensions() {
        this.lock.lock();
        try {
            return ImmutableMap.copyOf(this.extensions);
        } finally {
            this.lock.unlock();
        }
    }

    public void deserializeExtension(WalletExtension walletExtension, byte[] bArr) throws Exception {
        this.lock.lock();
        this.keyChainGroupLock.lock();
        try {
            walletExtension.deserializeWalletExtension(this, bArr);
            this.extensions.put(walletExtension.getWalletExtensionID(), walletExtension);
        } finally {
            try {
            } finally {
            }
        }
    }

    @Override
    public void setTag(String str, ByteString byteString) {
        super.setTag(str, byteString);
        saveNow();
    }

    private static class FeeCalculation {
        public TransactionOutput bestChangeOutput;
        public CoinSelection bestCoinSelection;

        private FeeCalculation() {
        }
    }

    public org.bitcoinj.wallet.Wallet.FeeCalculation calculateFee(org.bitcoinj.wallet.SendRequest r21, org.bitcoinj.core.Coin r22, java.util.List<org.bitcoinj.core.TransactionInput> r23, boolean r24, java.util.List<org.bitcoinj.core.TransactionOutput> r25) throws org.bitcoinj.core.InsufficientMoneyException {
        throw new UnsupportedOperationException("Method not decompiled: org.bitcoinj.wallet.Wallet.calculateFee(org.bitcoinj.wallet.SendRequest, org.bitcoinj.core.Coin, java.util.List, boolean, java.util.List):org.bitcoinj.wallet.Wallet$FeeCalculation");
    }

    private void resetTxInputs(SendRequest sendRequest, List<TransactionInput> list) {
        sendRequest.tx.clearInputs();
        Iterator<TransactionInput> it = list.iterator();
        while (it.hasNext()) {
            sendRequest.tx.addInput(it.next());
        }
    }

    private int estimateBytesForSigning(CoinSelection coinSelection) {
        Script script;
        Iterator<TransactionOutput> it = coinSelection.gathered.iterator();
        int i = 0;
        while (it.hasNext()) {
            try {
                Script scriptPubKey = it.next().getScriptPubKey();
                ECKey eCKey = null;
                if (scriptPubKey.isSentToAddress()) {
                    ECKey findKeyFromPubHash = findKeyFromPubHash(scriptPubKey.getPubKeyHash());
                    Preconditions.checkNotNull(findKeyFromPubHash, "Coin selection includes unspendable outputs");
                    eCKey = findKeyFromPubHash;
                    script = null;
                } else if (scriptPubKey.isPayToScriptHash()) {
                    script = findRedeemDataFromScriptHash(scriptPubKey.getPubKeyHash()).redeemScript;
                    Preconditions.checkNotNull(script, "Coin selection includes unspendable outputs");
                } else {
                    script = null;
                }
                i += scriptPubKey.getNumberOfBytesRequiredToSpend(eCKey, script);
            } catch (ScriptException e) {
                throw new IllegalStateException(e);
            }
        }
        return i;
    }

    public void setTransactionBroadcaster(@Nullable TransactionBroadcaster transactionBroadcaster) {
        Transaction[] transactionArr = new Transaction[0];
        this.lock.lock();
        try {
            if (this.vTransactionBroadcaster != transactionBroadcaster) {
                this.vTransactionBroadcaster = transactionBroadcaster;
                if (transactionBroadcaster != null) {
                    Transaction[] transactionArr2 = (Transaction[]) this.pending.values().toArray(transactionArr);
                    this.lock.unlock();
                    for (Transaction transaction : transactionArr2) {
                        TransactionConfidence.ConfidenceType confidenceType = transaction.getConfidence().getConfidenceType();
                        Preconditions.checkState(confidenceType == TransactionConfidence.ConfidenceType.PENDING || confidenceType == TransactionConfidence.ConfidenceType.IN_CONFLICT, "Expected PENDING or IN_CONFLICT, was %s.", new Object[]{confidenceType});
                        log.info("New broadcaster so uploading waiting tx {}", transaction.getHash());
                        transactionBroadcaster.broadcastTransaction(transaction);
                    }
                }
            }
        } finally {
            this.lock.unlock();
        }
    }

    public void setKeyRotationTime(Date date) {
        setKeyRotationTime(date.getTime() / 1000);
    }

    @Nullable
    public Date getKeyRotationTime() {
        long j = this.vKeyRotationTimestamp;
        if (j != 0) {
            return new Date(j * 1000);
        }
        return null;
    }

    public void setKeyRotationTime(long j) {
        Preconditions.checkArgument(j <= Utils.currentTimeSeconds(), "Given time (%s) cannot be in the future.", new Object[]{Utils.dateTimeFormat(1000 * j)});
        this.vKeyRotationTimestamp = j;
        saveNow();
    }

    public boolean isKeyRotating(ECKey eCKey) {
        long j = this.vKeyRotationTimestamp;
        return j != 0 && eCKey.getCreationTimeSeconds() < j;
    }

    @Deprecated
    public ListenableFuture<List<Transaction>> maybeDoMaintenance(@Nullable KeyParameter keyParameter, boolean z) throws DeterministicUpgradeRequiresPassword {
        return doMaintenance(keyParameter, z);
    }

    public ListenableFuture<List<Transaction>> doMaintenance(@Nullable KeyParameter keyParameter, boolean z) throws DeterministicUpgradeRequiresPassword {
        this.lock.lock();
        this.keyChainGroupLock.lock();
        try {
            List<Transaction> maybeRotateKeys = maybeRotateKeys(keyParameter, z);
            if (!z) {
                return Futures.immediateFuture(maybeRotateKeys);
            }
            this.keyChainGroupLock.unlock();
            this.lock.unlock();
            Preconditions.checkState(!this.lock.isHeldByCurrentThread());
            ArrayList arrayList = new ArrayList(maybeRotateKeys.size());
            TransactionBroadcaster transactionBroadcaster = this.vTransactionBroadcaster;
            Iterator<Transaction> it = maybeRotateKeys.iterator();
            while (it.hasNext()) {
                try {
                    ListenableFuture<Transaction> future = transactionBroadcaster.broadcastTransaction(it.next()).future();
                    arrayList.add(future);
                    Futures.addCallback(future, new FutureCallback<Transaction>() {
                        public void onSuccess(Transaction transaction) {
                            Wallet.log.info("Successfully broadcast key rotation tx: {}", transaction);
                        }

                        public void onFailure(Throwable th) {
                            Wallet.log.error("Failed to broadcast key rotation tx", th);
                        }
                    });
                } catch (Exception e) {
                    log.error("Failed to broadcast rekey tx", e);
                }
            }
            return Futures.allAsList(arrayList);
        } finally {
            this.keyChainGroupLock.unlock();
            this.lock.unlock();
        }
    }

    @GuardedBy("keyChainGroupLock")
    private List<Transaction> maybeRotateKeys(@Nullable KeyParameter keyParameter, boolean z) throws DeterministicUpgradeRequiresPassword {
        Transaction rekeyOneBatch;
        Preconditions.checkState(this.lock.isHeldByCurrentThread());
        Preconditions.checkState(this.keyChainGroupLock.isHeldByCurrentThread());
        LinkedList newLinkedList = Lists.newLinkedList();
        long j = this.vKeyRotationTimestamp;
        if (j == 0) {
            return newLinkedList;
        }
        boolean z2 = true;
        Iterator<DeterministicKeyChain> it = this.keyChainGroup.getDeterministicKeyChains().iterator();
        while (true) {
            if (!it.hasNext()) {
                break;
            }
            if (it.next().getEarliestKeyCreationTime() >= j) {
                z2 = false;
                break;
            }
        }
        if (z2) {
            try {
                if (this.keyChainGroup.getImportedKeys().isEmpty()) {
                    log.info("All HD chains are currently rotating and we have no random keys, creating fresh HD chain ...");
                    this.keyChainGroup.createAndActivateNewHDChain();
                } else {
                    log.info("All HD chains are currently rotating, attempting to create a new one from the next oldest non-rotating key material ...");
                    this.keyChainGroup.upgradeToDeterministic(j, keyParameter);
                    log.info(" ... upgraded to HD again, based on next best oldest key.");
                }
            } catch (AllRandomKeysRotating unused) {
                log.info(" ... no non-rotating random keys available, generating entirely new HD tree: backup required after this.");
                this.keyChainGroup.createAndActivateNewHDChain();
            }
            saveNow();
        }
        do {
            rekeyOneBatch = rekeyOneBatch(j, keyParameter, newLinkedList, z);
            if (rekeyOneBatch != null) {
                newLinkedList.add(rekeyOneBatch);
            }
            if (rekeyOneBatch == null) {
                break;
            }
        } while (rekeyOneBatch.getInputs().size() == 600);
        return newLinkedList;
    }

    @Nullable
    private Transaction rekeyOneBatch(long j, @Nullable KeyParameter keyParameter, List<Transaction> list, boolean z) {
        this.lock.lock();
        try {
            try {
                boolean z2 = true;
                FilteringCoinSelector filteringCoinSelector = new FilteringCoinSelector(new KeyTimeCoinSelector(this, j, true));
                Iterator<Transaction> it = list.iterator();
                while (it.hasNext()) {
                    filteringCoinSelector.excludeOutputsSpentBy(it.next());
                }
                CoinSelection select = filteringCoinSelector.select(Coin.ZERO, calculateAllSpendCandidates());
                if (!select.valueGathered.equals(Coin.ZERO)) {
                    maybeUpgradeToHD(keyParameter);
                    Transaction transaction = new Transaction(this.params);
                    Iterator<TransactionOutput> it2 = select.gathered.iterator();
                    while (it2.hasNext()) {
                        transaction.addInput(it2.next());
                    }
                    transaction.addOutput(select.valueGathered, z ? freshReceiveAddress() : currentReceiveAddress());
                    if (!adjustOutputDownwardsForFee(transaction, select, Transaction.DEFAULT_TX_FEE, true)) {
                        log.error("Failed to adjust rekey tx for fees.");
                    } else {
                        transaction.getConfidence().setSource(TransactionConfidence.Source.SELF);
                        transaction.setPurpose(Transaction.Purpose.KEY_ROTATION);
                        SendRequest forTx = SendRequest.forTx(transaction);
                        forTx.aesKey = keyParameter;
                        if (z) {
                            signTransaction(forTx);
                        }
                        if (transaction.unsafeBitcoinSerialize().length >= 100000) {
                            z2 = false;
                        }
                        Preconditions.checkState(z2);
                        return transaction;
                    }
                }
                return null;
            } catch (VerificationException e) {
                throw new RuntimeException(e);
            }
        } finally {
            this.lock.unlock();
        }
    }
}