diff --git a/common/pom.xml b/common/pom.xml
index 08dda08f9b..4019d18c02 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/core/pom.xml b/core/pom.xml
index 4e40eb87ab..1bb260f611 100755
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -6,7 +6,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
core
diff --git a/core/src/main/java/io/bitsquare/btc/WalletService.java b/core/src/main/java/io/bitsquare/btc/WalletService.java
deleted file mode 100644
index ccaac1bfcd..0000000000
--- a/core/src/main/java/io/bitsquare/btc/WalletService.java
+++ /dev/null
@@ -1,1201 +0,0 @@
-/*
- * This file is part of Bitsquare.
- *
- * Bitsquare is free software: you can redistribute it and/or modify it
- * under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or (at
- * your option) any later version.
- *
- * Bitsquare is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
- * License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with Bitsquare. If not, see .
- */
-
-package io.bitsquare.btc;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableList;
-import com.google.common.util.concurrent.FutureCallback;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.Service;
-import com.runjva.sourceforge.jsocks.protocol.Socks5Proxy;
-import io.bitsquare.app.Log;
-import io.bitsquare.btc.listeners.AddressConfidenceListener;
-import io.bitsquare.btc.listeners.BalanceListener;
-import io.bitsquare.btc.listeners.TxConfidenceListener;
-import io.bitsquare.common.Timer;
-import io.bitsquare.common.UserThread;
-import io.bitsquare.common.handlers.ErrorMessageHandler;
-import io.bitsquare.common.handlers.ExceptionHandler;
-import io.bitsquare.common.handlers.ResultHandler;
-import io.bitsquare.network.Socks5ProxyProvider;
-import io.bitsquare.storage.FileUtil;
-import io.bitsquare.storage.Storage;
-import io.bitsquare.user.Preferences;
-import javafx.beans.property.*;
-import org.bitcoinj.core.*;
-import org.bitcoinj.crypto.DeterministicKey;
-import org.bitcoinj.crypto.KeyCrypterScrypt;
-import org.bitcoinj.net.discovery.SeedPeers;
-import org.bitcoinj.params.MainNetParams;
-import org.bitcoinj.params.RegTestParams;
-import org.bitcoinj.params.TestNet3Params;
-import org.bitcoinj.utils.Threading;
-import org.bitcoinj.wallet.DeterministicSeed;
-import org.jetbrains.annotations.NotNull;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.spongycastle.crypto.params.KeyParameter;
-
-import javax.annotation.Nullable;
-import javax.inject.Inject;
-import javax.inject.Named;
-import java.io.File;
-import java.io.IOException;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.UnknownHostException;
-import java.nio.file.Paths;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArraySet;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-
-import static com.google.common.base.Preconditions.checkNotNull;
-
-/**
- * WalletService handles all non trade specific wallet and bitcoin related services.
- * It startup the wallet app kit and initialized the wallet.
- */
-public class WalletService {
- private static final Logger log = LoggerFactory.getLogger(WalletService.class);
-
- private static final long STARTUP_TIMEOUT_SEC = 60;
-
- private final CopyOnWriteArraySet addressConfidenceListeners = new CopyOnWriteArraySet<>();
- private final CopyOnWriteArraySet txConfidenceListeners = new CopyOnWriteArraySet<>();
- private final CopyOnWriteArraySet balanceListeners = new CopyOnWriteArraySet<>();
-
- private final DownloadListener downloadListener = new DownloadListener();
- private final WalletEventListener walletEventListener = new BitsquareWalletEventListener();
-
- private final RegTestHost regTestHost;
- private final TradeWalletService tradeWalletService;
- private final AddressEntryList addressEntryList;
- private final Preferences preferences;
- private final Socks5ProxyProvider socks5ProxyProvider;
- private final NetworkParameters params;
- private final File walletDir;
- private final UserAgent userAgent;
-
- private WalletAppKitBitSquare walletAppKit;
- private Wallet wallet;
- private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
- private final ObjectProperty> connectedPeers = new SimpleObjectProperty<>();
- public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
- private final Storage storage;
- private final Long bloomFilterTweak;
- private KeyParameter aesKey;
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Constructor
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- @Inject
- public WalletService(RegTestHost regTestHost,
- TradeWalletService tradeWalletService,
- AddressEntryList addressEntryList,
- UserAgent userAgent,
- Preferences preferences,
- Socks5ProxyProvider socks5ProxyProvider,
- @Named(BtcOptionKeys.WALLET_DIR) File appDir) {
- this.regTestHost = regTestHost;
- this.tradeWalletService = tradeWalletService;
- this.addressEntryList = addressEntryList;
- this.preferences = preferences;
- this.socks5ProxyProvider = socks5ProxyProvider;
- this.params = preferences.getBitcoinNetwork().getParameters();
- this.walletDir = new File(appDir, "bitcoin");
- this.userAgent = userAgent;
-
- storage = new Storage<>(walletDir);
- Long persisted = storage.initAndGetPersistedWithFileName("BloomFilterNonce");
- if (persisted != null) {
- bloomFilterTweak = persisted;
- } else {
- bloomFilterTweak = new Random().nextLong();
- storage.queueUpForSave(bloomFilterTweak, 100);
- }
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Public Methods
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void initialize(@Nullable DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
- Log.traceCall();
- // Tell bitcoinj to execute event handlers on the JavaFX UI thread. This keeps things simple and means
- // we cannot forget to switch threads when adding event handlers. Unfortunately, the DownloadListener
- // we give to the app kit is currently an exception and runs on a library thread. It'll get fixed in
- // a future version.
-
- Threading.USER_THREAD = UserThread.getExecutor();
-
- Timer timeoutTimer = UserThread.runAfter(() ->
- exceptionHandler.handleException(new TimeoutException("Wallet did not initialize in " +
- STARTUP_TIMEOUT_SEC + " seconds.")), STARTUP_TIMEOUT_SEC);
-
- backupWallet();
-
- final Socks5Proxy socks5Proxy = preferences.getUseTorForBitcoinJ() ? socks5ProxyProvider.getSocks5Proxy() : null;
- log.debug("Use socks5Proxy for bitcoinj: " + socks5Proxy);
-
- // If seed is non-null it means we are restoring from backup.
- walletAppKit = new WalletAppKitBitSquare(params, socks5Proxy, walletDir, "Bitsquare") {
- @Override
- protected void onSetupCompleted() {
- // Don't make the user wait for confirmations for now, as the intention is they're sending it
- // their own money!
- walletAppKit.wallet().allowSpendingUnconfirmedTransactions();
- if (params != RegTestParams.get())
- walletAppKit.peerGroup().setMaxConnections(11);
-
- wallet = walletAppKit.wallet();
- wallet.addEventListener(walletEventListener);
-
- addressEntryList.onWalletReady(wallet);
-
-
- walletAppKit.peerGroup().addEventListener(new PeerEventListener() {
- @Override
- public void onPeersDiscovered(Set peerAddresses) {
- }
-
- @Override
- public void onBlocksDownloaded(Peer peer, Block block, FilteredBlock filteredBlock, int blocksLeft) {
- }
-
- @Override
- public void onChainDownloadStarted(Peer peer, int blocksLeft) {
- }
-
- @Override
- public void onPeerConnected(Peer peer, int peerCount) {
- numPeers.set(peerCount);
- connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
- }
-
- @Override
- public void onPeerDisconnected(Peer peer, int peerCount) {
- numPeers.set(peerCount);
- connectedPeers.set(walletAppKit.peerGroup().getConnectedPeers());
- }
-
- @Override
- public Message onPreMessageReceived(Peer peer, Message m) {
- return null;
- }
-
- @Override
- public void onTransaction(Peer peer, Transaction t) {
- }
-
- @Nullable
- @Override
- public List getData(Peer peer, GetDataMessage m) {
- return null;
- }
- });
-
- // set after wallet is ready
- tradeWalletService.setWalletAppKit(walletAppKit);
- tradeWalletService.setAddressEntryList(addressEntryList);
- timeoutTimer.stop();
-
- // onSetupCompleted in walletAppKit is not the called on the last invocations, so we add a bit of delay
- UserThread.runAfter(resultHandler::handleResult, 100, TimeUnit.MILLISECONDS);
- }
- };
-
- // Bloom filters in BitcoinJ are completely broken
- // See: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/
- // Here are a few improvements to fix a few vulnerabilities.
-
- // Bitsquare's BitcoinJ fork has added a bloomFilterTweak (nonce) setter to reuse the same seed avoiding the trivial vulnerability
- // by getting the real pub keys by intersections of several filters sent at each startup.
- walletAppKit.setBloomFilterTweak(bloomFilterTweak);
-
- // Avoid the simple attack (see: https://jonasnick.github.io/blog/2015/02/12/privacy-in-bitcoinj/) due to the
- // default implementation using both pubkey and hash of pubkey. We have set a insertPubKey flag in BasicKeyChain to default false.
-
- // Default only 266 keys are generated (2 * 100+33). That would trigger new bloom filters when we are reaching
- // the threshold. To avoid reaching the threshold we create much more keys which are unlikely to cause update of the
- // filter for most users. With lookaheadSize of 500 we get 1333 keys which should be enough for most users to
- // never need to update a bloom filter, which would weaken privacy.
- walletAppKit.setLookaheadSize(500);
-
- // Calculation is derived from: https://www.reddit.com/r/Bitcoin/comments/2vrx6n/privacy_in_bitcoinj_android_wallet_multibit_hive/coknjuz
- // No. of false positives (56M keys in the blockchain):
- // First attempt for FP rate:
- // FP rate = 0,0001; No. of false positives: 0,0001 * 56 000 000 = 5600
- // We have 1333keys: 1333 / (5600 + 1333) = 0.19 -> 19 % probability that a pub key is in our wallet
- // After tests I found out that the bandwidth consumption varies widely related to the generated filter.
- // About 20- 40 MB for upload and 30-130 MB for download at first start up (spv chain).
- // Afterwards its about 1 MB for upload and 20-80 MB for download.
- // Probably better then a high FP rate would be to include foreign pubKeyHashes which are tested to not be used
- // in many transactions. If we had a pool of 100 000 such keys (2 MB data dump) to random select 4000 we could mix it with our
- // 1000 own keys and get a similar probability rate as with the current setup but less variation in bandwidth
- // consumption.
-
- // For now to reduce risks with high bandwidth consumption we reduce the FP rate by half.
- // FP rate = 0,00005; No. of false positives: 0,00005 * 56 000 000 = 2800
- // 1333 / (2800 + 1333) = 0.32 -> 32 % probability that a pub key is in our wallet
- walletAppKit.setBloomFilterFalsePositiveRate(0.00005);
-
- String btcNodes = preferences.getBitcoinNodes();
- log.debug("btcNodes: " + btcNodes);
- boolean usePeerNodes = false;
-
- // Pass custom seed nodes if set in options
- if (!btcNodes.isEmpty()) {
-
- // TODO: this parsing should be more robust,
- // give validation error if needed.
- String[] nodes = btcNodes.replace(", ", ",").split(",");
- List peerAddressList = new ArrayList<>();
- for (String node : nodes) {
- String[] parts = node.split(":");
- if (parts.length == 1) {
- // port not specified. Use default port for network.
- parts = new String[]{parts[0], Integer.toString(params.getPort())};
- }
- if (parts.length == 2) {
- // note: this will cause a DNS request if hostname used.
- // note: DNS requests are routed over socks5 proxy, if used.
- // fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
- InetSocketAddress addr;
- if (socks5Proxy != null) {
- InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
- // proxy remote DNS request happens here.
- addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
- } else {
- // DNS request happens here. if it fails, addr.isUnresolved() == true.
- addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
- }
- // note: isUnresolved check should be removed once we fix PeerAddress
- if (addr != null && !addr.isUnresolved())
- peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
- }
- }
- if (peerAddressList.size() > 0) {
- PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
- log.debug("btcNodes parsed: " + Arrays.toString(peerAddressListFixed));
-
- walletAppKit.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
- usePeerNodes = true;
- }
- }
-
- // Now configure and start the appkit. This will take a second or two - we could show a temporary splash screen
- // or progress widget to keep the user engaged whilst we initialise, but we don't.
- if (params == RegTestParams.get()) {
- if (regTestHost == RegTestHost.REG_TEST_SERVER) {
- try {
- walletAppKit.setPeerNodes(new PeerAddress(InetAddress.getByName(RegTestHost.SERVER_IP), params.getPort()));
- usePeerNodes = true;
- } catch (UnknownHostException e) {
- throw new RuntimeException(e);
- }
- } else if (regTestHost == RegTestHost.LOCALHOST) {
- walletAppKit.connectToLocalHost(); // You should run a regtest mode bitcoind locally.}
- }
- } else if (params == MainNetParams.get()) {
- // Checkpoints are block headers that ship inside our app: for a new user, we pick the last header
- // in the checkpoints file and then download the rest from the network. It makes things much faster.
- // Checkpoint files are made using the BuildCheckpoints tool and usually we have to download the
- // last months worth or more (takes a few seconds).
- try {
- walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints"));
- } catch (Exception e) {
- e.printStackTrace();
- log.error(e.toString());
- }
- } else if (params == TestNet3Params.get()) {
- walletAppKit.setCheckpoints(getClass().getResourceAsStream("/wallet/checkpoints.testnet"));
- }
-
- // If operating over a proxy and we haven't set any peer nodes, then
- // we want to use SeedPeers for discovery instead of the default DnsDiscovery.
- // This is only because we do not yet have a Dns discovery class that works
- // reliably over proxy/tor.
- //
- // todo: There should be a user pref called "Use Local DNS for Proxy/Tor"
- // that disables this. In that case, the default DnsDiscovery class will
- // be used which should work, but is less private. The aim here is to
- // be private by default when using proxy/tor. However, the seedpeers
- // could become outdated, so it is important that the user be able to
- // disable it, but should be made aware of the reduced privacy.
- if (socks5Proxy != null && !usePeerNodes) {
- // SeedPeersSocks5Dns should replace SeedPeers once working reliably.
- // SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
- walletAppKit.setDiscovery(new SeedPeers(params));
- }
-
- walletAppKit.setDownloadListener(downloadListener)
- .setBlockingStartup(false)
- .setUserAgent(userAgent.getName(), userAgent.getVersion())
- .restoreWalletFromSeed(seed);
-
- walletAppKit.addListener(new Service.Listener() {
- @Override
- public void failed(@NotNull Service.State from, @NotNull Throwable failure) {
- walletAppKit = null;
- log.error("walletAppKit failed");
- timeoutTimer.stop();
- UserThread.execute(() -> exceptionHandler.handleException(failure));
- }
- }, Threading.USER_THREAD);
- walletAppKit.startAsync();
- }
-
- public void shutDown() {
- if (wallet != null)
- wallet.removeEventListener(walletEventListener);
-
- if (walletAppKit != null) {
- try {
- walletAppKit.stopAsync();
- walletAppKit.awaitTerminated(5, TimeUnit.SECONDS);
- } catch (Throwable e) {
- // ignore
- }
- shutDownDone.set(true);
- }
- }
-
- public String exportWalletData(boolean includePrivKeys) {
- StringBuilder addressEntryListData = new StringBuilder();
- getAddressEntryListAsImmutableList().stream().forEach(e -> addressEntryListData.append(e.toString()).append("\n"));
- return "BitcoinJ wallet:\n" +
- wallet.toString(includePrivKeys, true, true, walletAppKit.chain()) + "\n\n" +
- "Bitsquare address entry list:\n" +
- addressEntryListData.toString() +
- "All pubkeys as hex:\n" +
- wallet.printAllPubKeysAsHex();
- }
-
- public void restoreSeedWords(DeterministicSeed seed, ResultHandler resultHandler, ExceptionHandler exceptionHandler) {
- Context ctx = Context.get();
- new Thread(() -> {
- try {
- Context.propagate(ctx);
- walletAppKit.stopAsync();
- walletAppKit.awaitTerminated();
- initialize(seed, resultHandler, exceptionHandler);
- } catch (Throwable t) {
- t.printStackTrace();
- log.error("Executing task failed. " + t.getMessage());
- }
- }, "RestoreWallet-%d").start();
- }
-
- public void backupWallet() {
- FileUtil.rollingBackup(walletDir, "Bitsquare.wallet", 20);
- }
-
- public void clearBackup() {
- try {
- FileUtil.deleteDirectory(new File(Paths.get(walletDir.getAbsolutePath(), "backup").toString()));
- } catch (IOException e) {
- log.error("Could not delete directory " + e.getMessage());
- e.printStackTrace();
- }
- }
-
- public void setAesKey(KeyParameter aesKey) {
- this.aesKey = aesKey;
- }
-
- public void decryptWallet(@NotNull KeyParameter key) {
- wallet.decrypt(key);
- addressEntryList.stream().forEach(e -> {
-
- final DeterministicKey keyPair = e.getKeyPair();
- if (keyPair != null && keyPair.isEncrypted())
- e.setDeterministicKey(keyPair.decrypt(key));
- });
-
- setAesKey(null);
- addressEntryList.queueUpForSave();
- }
-
- public void encryptWallet(KeyCrypterScrypt keyCrypterScrypt, KeyParameter key) {
- if (this.aesKey != null) {
- log.warn("encryptWallet called but we have a aesKey already set. " +
- "We decryptWallet with the old key before we apply the new key.");
- decryptWallet(this.aesKey);
- }
-
- wallet.encrypt(keyCrypterScrypt, key);
- addressEntryList.stream().forEach(e -> {
- final DeterministicKey keyPair = e.getKeyPair();
- if (keyPair != null && keyPair.isEncrypted())
- e.setDeterministicKey(keyPair.encrypt(keyCrypterScrypt, key));
- });
- setAesKey(key);
- addressEntryList.queueUpForSave();
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Listener
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void addAddressConfidenceListener(AddressConfidenceListener listener) {
- addressConfidenceListeners.add(listener);
- }
-
- public void removeAddressConfidenceListener(AddressConfidenceListener listener) {
- addressConfidenceListeners.remove(listener);
- }
-
- public void addTxConfidenceListener(TxConfidenceListener listener) {
- txConfidenceListeners.add(listener);
- }
-
- public void removeTxConfidenceListener(TxConfidenceListener listener) {
- txConfidenceListeners.remove(listener);
- }
-
- public void addBalanceListener(BalanceListener listener) {
- balanceListeners.add(listener);
- }
-
- public void removeBalanceListener(BalanceListener listener) {
- balanceListeners.remove(listener);
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // AddressEntry
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public AddressEntry getOrCreateAddressEntry(String offerId, AddressEntry.Context context) {
- Optional addressEntry = getAddressEntryListAsImmutableList().stream()
- .filter(e -> offerId.equals(e.getOfferId()))
- .filter(e -> context == e.getContext())
- .findAny();
- if (addressEntry.isPresent())
- return addressEntry.get();
- else
- return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context, offerId));
- }
-
- public AddressEntry getOrCreateAddressEntry(AddressEntry.Context context) {
- Optional addressEntry = getAddressEntryListAsImmutableList().stream()
- .filter(e -> context == e.getContext())
- .findAny();
- if (addressEntry.isPresent())
- return addressEntry.get();
- else
- return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
- }
-
- public AddressEntry getOrCreateUnusedAddressEntry(AddressEntry.Context context) {
- Optional addressEntry = getAddressEntryListAsImmutableList().stream()
- .filter(e -> context == e.getContext())
- .filter(e -> getNumTxOutputsForAddress(e.getAddress()) == 0)
- .findAny();
- if (addressEntry.isPresent())
- return addressEntry.get();
- else
- return addressEntryList.addAddressEntry(new AddressEntry(wallet.freshReceiveKey(), wallet.getParams(), context));
- }
-
- public Optional findAddressEntry(String address, AddressEntry.Context context) {
- return getAddressEntryListAsImmutableList().stream()
- .filter(e -> address.equals(e.getAddressString()))
- .filter(e -> context == e.getContext())
- .findAny();
- }
-
- public List getAvailableAddressEntries() {
- return getAddressEntryListAsImmutableList().stream()
- .filter(addressEntry -> AddressEntry.Context.AVAILABLE == addressEntry.getContext())
- .collect(Collectors.toList());
- }
-
- public List getAddressEntries(AddressEntry.Context context) {
- return getAddressEntryListAsImmutableList().stream()
- .filter(addressEntry -> context == addressEntry.getContext())
- .collect(Collectors.toList());
- }
-
- public List getFundedAvailableAddressEntries() {
- return getAvailableAddressEntries().stream()
- .filter(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).isPositive())
- .collect(Collectors.toList());
- }
-
- public List getAddressEntryListAsImmutableList() {
- return ImmutableList.copyOf(addressEntryList);
- }
-
- public void swapTradeEntryToAvailableEntry(String offerId, AddressEntry.Context context) {
- Optional addressEntryOptional = getAddressEntryListAsImmutableList().stream()
- .filter(e -> offerId.equals(e.getOfferId()))
- .filter(e -> context == e.getContext())
- .findAny();
- addressEntryOptional.ifPresent(addressEntryList::swapToAvailable);
- }
-
- public void swapAnyTradeEntryContextToAvailableEntry(String offerId) {
- swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.OFFER_FUNDING);
- swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.RESERVED_FOR_TRADE);
- swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.MULTI_SIG);
- swapTradeEntryToAvailableEntry(offerId, AddressEntry.Context.TRADE_PAYOUT);
- }
-
- public void saveAddressEntryList() {
- addressEntryList.queueUpForSave();
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // TransactionConfidence
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public TransactionConfidence getConfidenceForAddress(Address address) {
- List transactionConfidenceList = new ArrayList<>();
- if (wallet != null) {
- Set transactions = wallet.getTransactions(true);
- if (transactions != null) {
- transactionConfidenceList.addAll(transactions.stream().map(tx ->
- getTransactionConfidence(tx, address)).collect(Collectors.toList()));
- }
- }
- return getMostRecentConfidence(transactionConfidenceList);
- }
-
- public TransactionConfidence getConfidenceForTxId(String txId) {
- if (wallet != null) {
- Set transactions = wallet.getTransactions(true);
- for (Transaction tx : transactions) {
- if (tx.getHashAsString().equals(txId))
- return tx.getConfidence();
- }
- }
- return null;
- }
-
- private TransactionConfidence getTransactionConfidence(Transaction tx, Address address) {
- List mergedOutputs = getOutputsWithConnectedOutputs(tx);
- List transactionConfidenceList = new ArrayList<>();
-
- mergedOutputs.stream().filter(e -> e.getScriptPubKey().isSentToAddress() ||
- e.getScriptPubKey().isPayToScriptHash()).forEach(transactionOutput -> {
- Address outputAddress = transactionOutput.getScriptPubKey().getToAddress(params);
- if (address.equals(outputAddress)) {
- transactionConfidenceList.add(tx.getConfidence());
- }
- });
- return getMostRecentConfidence(transactionConfidenceList);
- }
-
-
- private List getOutputsWithConnectedOutputs(Transaction tx) {
- List transactionOutputs = tx.getOutputs();
- List connectedOutputs = new ArrayList<>();
-
- // add all connected outputs from any inputs as well
- List transactionInputs = tx.getInputs();
- for (TransactionInput transactionInput : transactionInputs) {
- TransactionOutput transactionOutput = transactionInput.getConnectedOutput();
- if (transactionOutput != null) {
- connectedOutputs.add(transactionOutput);
- }
- }
-
- List mergedOutputs = new ArrayList<>();
- mergedOutputs.addAll(transactionOutputs);
- mergedOutputs.addAll(connectedOutputs);
- return mergedOutputs;
- }
-
-
- private TransactionConfidence getMostRecentConfidence(List transactionConfidenceList) {
- TransactionConfidence transactionConfidence = null;
- for (TransactionConfidence confidence : transactionConfidenceList) {
- if (confidence != null) {
- if (transactionConfidence == null ||
- confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.PENDING) ||
- (confidence.getConfidenceType().equals(TransactionConfidence.ConfidenceType.BUILDING) &&
- transactionConfidence.getConfidenceType().equals(
- TransactionConfidence.ConfidenceType.BUILDING) &&
- confidence.getDepthInBlocks() < transactionConfidence.getDepthInBlocks())) {
- transactionConfidence = confidence;
- }
- }
- }
- return transactionConfidence;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Balance
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- // BalanceType.AVAILABLE
- public Coin getAvailableBalance() {
- return wallet != null ? wallet.getBalance(Wallet.BalanceType.AVAILABLE) : Coin.ZERO;
- }
-
- public Coin getBalanceForAddress(Address address) {
- return wallet != null ? getBalance(wallet.calculateAllSpendCandidates(), address) : Coin.ZERO;
- }
-
- private Coin getBalance(List transactionOutputs, Address address) {
- Coin balance = Coin.ZERO;
- for (TransactionOutput transactionOutput : transactionOutputs) {
- if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
- Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
- if (addressOutput.equals(address))
- balance = balance.add(transactionOutput.getValue());
- }
- }
- return balance;
- }
-
- public Coin getSavingWalletBalance() {
- return Coin.valueOf(getFundedAvailableAddressEntries().stream()
- .mapToLong(addressEntry -> getBalanceForAddress(addressEntry.getAddress()).value)
- .sum());
- }
-
- public int getNumTxOutputsForAddress(Address address) {
- List transactionOutputs = new ArrayList<>();
- wallet.getTransactions(true).stream().forEach(t -> transactionOutputs.addAll(t.getOutputs()));
- int outputs = 0;
- for (TransactionOutput transactionOutput : transactionOutputs) {
- if (transactionOutput.getScriptPubKey().isSentToAddress() || transactionOutput.getScriptPubKey().isPayToScriptHash()) {
- Address addressOutput = transactionOutput.getScriptPubKey().getToAddress(params);
- if (addressOutput.equals(address))
- outputs++;
- }
- }
- return outputs;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Double spend unconfirmed transaction (unlock in case we got into a tx with a too low mining fee)
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public void doubleSpendTransaction(String txId, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientMoneyException, AddressFormatException, AddressEntryException {
- AddressEntry addressEntry = getOrCreateUnusedAddressEntry(AddressEntry.Context.AVAILABLE);
- checkNotNull(addressEntry.getAddress(), "addressEntry.getAddress() must not be null");
- Optional transactionOptional = wallet.getTransactions(true).stream()
- .filter(t -> t.getHashAsString().equals(txId))
- .findAny();
- if (transactionOptional.isPresent())
- doubleSpendTransaction(transactionOptional.get(), addressEntry.getAddress(), resultHandler, errorMessageHandler);
- }
-
- public void doubleSpendTransaction(Transaction txToDoubleSpend, Address toAddress, Runnable resultHandler, ErrorMessageHandler errorMessageHandler) throws InsufficientMoneyException, AddressFormatException, AddressEntryException {
- final TransactionConfidence.ConfidenceType confidenceType = txToDoubleSpend.getConfidence().getConfidenceType();
- if (confidenceType == TransactionConfidence.ConfidenceType.PENDING) {
- log.debug("txToDoubleSpend no. of inputs " + txToDoubleSpend.getInputs().size());
-
- Transaction newTransaction = new Transaction(params);
- txToDoubleSpend.getInputs().stream().forEach(input -> {
- final TransactionOutput connectedOutput = input.getConnectedOutput();
- if (connectedOutput != null &&
- connectedOutput.isMine(wallet) &&
- connectedOutput.getParentTransaction() != null &&
- connectedOutput.getParentTransaction().getConfidence() != null &&
- input.getValue() != null) {
- if (connectedOutput.getParentTransaction().getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) {
- newTransaction.addInput(new TransactionInput(params,
- newTransaction,
- new byte[]{},
- new TransactionOutPoint(params, input.getOutpoint().getIndex(),
- new Transaction(params, connectedOutput.getParentTransaction().bitcoinSerialize())),
- Coin.valueOf(input.getValue().value)));
- } else {
- log.warn("Confidence of parent tx is not of type BUILDING: ConfidenceType=" +
- connectedOutput.getParentTransaction().getConfidence().getConfidenceType());
- }
- }
- }
- );
-
- log.debug("newTransaction no. of inputs " + newTransaction.getInputs().size());
- log.debug("newTransaction size in kB " + newTransaction.bitcoinSerialize().length / 1024);
-
- if (!newTransaction.getInputs().isEmpty()) {
- Coin amount = Coin.valueOf(newTransaction.getInputs().stream()
- .mapToLong(input -> input.getValue() != null ? input.getValue().value : 0)
- .sum());
- newTransaction.addOutput(amount, toAddress);
-
- Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(newTransaction);
- sendRequest.aesKey = aesKey;
- sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress, false);
- sendRequest.changeAddress = toAddress;
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
-
- Coin requiredFee = getFeeForDoubleSpend(sendRequest,
- toAddress,
- amount,
- FeePolicy.getFixedTxFeeForTrades());
-
- amount = (amount.subtract(requiredFee)).subtract(FeePolicy.getFixedTxFeeForTrades());
- newTransaction.clearOutputs();
- newTransaction.addOutput(amount, toAddress);
-
- sendRequest = Wallet.SendRequest.forTx(newTransaction);
- sendRequest.aesKey = aesKey;
- sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress, false);
- // We don't expect change but set it just in case
- sendRequest.changeAddress = toAddress;
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
-
- Wallet.SendResult sendResult = null;
- try {
- sendResult = wallet.sendCoins(sendRequest);
- } catch (InsufficientMoneyException e) {
- // in some cases getFee did not calculate correctly and we still get an InsufficientMoneyException
- log.warn("We still have a missing fee " + (e.missing != null ? e.missing.toFriendlyString() : ""));
-
- if (e != null)
- amount = amount.subtract(e.missing);
- newTransaction.clearOutputs();
- newTransaction.addOutput(amount, toAddress);
-
- sendRequest = Wallet.SendRequest.forTx(newTransaction);
- sendRequest.aesKey = aesKey;
- sendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress, false);
- sendRequest.changeAddress = toAddress;
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
-
- try {
- sendResult = wallet.sendCoins(sendRequest);
- } catch (InsufficientMoneyException e2) {
- errorMessageHandler.handleErrorMessage("We did not get the correct fee calculated. " + (e2.missing != null ? e2.missing.toFriendlyString() : ""));
- }
- }
- if (sendResult != null) {
- log.debug("Broadcasting double spending transaction. " + newTransaction);
- Futures.addCallback(sendResult.broadcastComplete, new FutureCallback() {
- @Override
- public void onSuccess(Transaction result) {
- log.info("Double spending transaction published. " + result);
- resultHandler.run();
- }
-
- @Override
- public void onFailure(@NotNull Throwable t) {
- log.info("Broadcasting double spending transaction failed. " + t.getMessage());
- errorMessageHandler.handleErrorMessage(t.getMessage());
- }
- });
- }
- } else {
- errorMessageHandler.handleErrorMessage("We could not find inputs we control in the transaction we want to double spend.");
- }
- } else if (confidenceType == TransactionConfidence.ConfidenceType.BUILDING) {
- errorMessageHandler.handleErrorMessage("That transaction is already in the blockchain so we cannot double spend it.");
- } else if (confidenceType == TransactionConfidence.ConfidenceType.DEAD) {
- errorMessageHandler.handleErrorMessage("One of the inputs of that transaction has been already double spent.");
- }
- }
-
- private Coin getFeeForDoubleSpend(Wallet.SendRequest sendRequest,
- Address toAddress,
- Coin amount,
- Coin fee) throws AddressEntryException, AddressFormatException {
- try {
- sendRequest.tx.clearOutputs();
- sendRequest.tx.addOutput(amount, toAddress);
-
- Wallet.SendRequest newSendRequest = Wallet.SendRequest.forTx(sendRequest.tx);
- newSendRequest.aesKey = aesKey;
- newSendRequest.coinSelector = new TradeWalletCoinSelector(params, toAddress);
- newSendRequest.changeAddress = toAddress;
- newSendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
- wallet.completeTx(newSendRequest);
-
- log.debug("After fee check: amount " + amount.toFriendlyString());
- log.debug("Output fee " + sendRequest.tx.getFee().toFriendlyString());
- sendRequest.tx.getOutputs().stream().forEach(o -> log.debug("Output value " + o.getValue().toFriendlyString()));
- } catch (InsufficientMoneyException e) {
- if (e.missing != null) {
- log.trace("missing fee " + e.missing.toFriendlyString());
- fee = fee.add(e.missing);
- amount = amount.subtract(fee);
- return getFeeForDoubleSpend(sendRequest,
- toAddress,
- amount,
- fee);
- }
- }
- return fee;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Withdrawal Fee calculation
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public Coin getRequiredFee(String fromAddress,
- String toAddress,
- Coin amount,
- AddressEntry.Context context)
- throws AddressFormatException, AddressEntryException {
- Optional addressEntry = findAddressEntry(fromAddress, context);
- if (!addressEntry.isPresent())
- throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
-
- checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must nto be null");
- return getFee(fromAddress,
- toAddress,
- amount,
- context,
- Coin.ZERO);
- }
-
- public Coin getRequiredFeeForMultipleAddresses(Set fromAddresses,
- String toAddress,
- Coin amount)
- throws AddressFormatException, AddressEntryException, InsufficientFundsException {
- Set addressEntries = fromAddresses.stream()
- .map(address -> {
- Optional addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.TRADE_PAYOUT);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.ARBITRATOR);
- return addressEntryOptional;
- })
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(Collectors.toSet());
- if (addressEntries.isEmpty())
- throw new AddressEntryException("No Addresses for withdraw found in our wallet");
- return getFeeForMultipleAddresses(fromAddresses,
- toAddress,
- amount,
- Coin.ZERO);
- }
-
-
- private Coin getFee(String fromAddress,
- String toAddress,
- Coin amount,
- AddressEntry.Context context,
- Coin fee) throws AddressEntryException, AddressFormatException {
- try {
- wallet.completeTx(getSendRequest(fromAddress, toAddress, amount, aesKey, context));
- } catch (InsufficientMoneyException e) {
- if (e.missing != null) {
- log.trace("missing fee " + e.missing.toFriendlyString());
- fee = fee.add(e.missing);
- amount = amount.subtract(fee);
- return getFee(fromAddress,
- toAddress,
- amount,
- context,
- fee);
- }
- }
- log.trace("result fee " + fee.toFriendlyString());
- return fee;
- }
-
- private Coin getFeeForMultipleAddresses(Set fromAddresses,
- String toAddress,
- Coin amount,
- Coin fee) throws AddressEntryException, AddressFormatException, InsufficientFundsException {
- try {
- wallet.completeTx(getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey));
- } catch (InsufficientMoneyException e) {
- if (e.missing != null) {
- log.trace("missing fee " + e.missing.toFriendlyString());
- fee = fee.add(e.missing);
- amount = amount.subtract(fee);
- if (amount.isGreaterThan(Transaction.MIN_NONDUST_OUTPUT)) {
- return getFeeForMultipleAddresses(fromAddresses,
- toAddress,
- amount,
- fee);
- } else {
- throw new InsufficientFundsException("The fees for that transaction exceed the available funds " +
- "or the resulting output value is below the min. dust value:\n" +
- "Missing " + e.missing.toFriendlyString());
- }
- }
- }
- log.trace("result fee " + fee.toFriendlyString());
- return fee;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Withdrawal Send
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public String sendFunds(String fromAddress,
- String toAddress,
- Coin receiverAmount,
- @Nullable KeyParameter aesKey,
- AddressEntry.Context context,
- FutureCallback callback) throws AddressFormatException,
- AddressEntryException, InsufficientMoneyException {
- Wallet.SendResult sendResult = wallet.sendCoins(getSendRequest(fromAddress, toAddress, receiverAmount, aesKey, context));
- Futures.addCallback(sendResult.broadcastComplete, callback);
-
- printTxWithInputs("sendFunds", sendResult.tx);
- return sendResult.tx.getHashAsString();
- }
-
- public String sendFundsForMultipleAddresses(Set fromAddresses,
- String toAddress,
- Coin receiverAmount,
- @Nullable String changeAddress,
- @Nullable KeyParameter aesKey,
- FutureCallback callback) throws AddressFormatException,
- AddressEntryException, InsufficientMoneyException {
- Wallet.SendResult sendResult = wallet.sendCoins(getSendRequestForMultipleAddresses(fromAddresses, toAddress,
- receiverAmount, changeAddress, aesKey));
- Futures.addCallback(sendResult.broadcastComplete, callback);
-
- printTxWithInputs("sendFunds", sendResult.tx);
- return sendResult.tx.getHashAsString();
- }
-
- public void emptyWallet(String toAddress, KeyParameter aesKey, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler)
- throws InsufficientMoneyException, AddressFormatException {
- Wallet.SendRequest sendRequest = Wallet.SendRequest.emptyWallet(new Address(params, toAddress));
- sendRequest.aesKey = aesKey;
- Wallet.SendResult sendResult = wallet.sendCoins(sendRequest);
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
- Futures.addCallback(sendResult.broadcastComplete, new FutureCallback() {
- @Override
- public void onSuccess(Transaction result) {
- resultHandler.handleResult();
- }
-
- @Override
- public void onFailure(@NotNull Throwable t) {
- errorMessageHandler.handleErrorMessage(t.getMessage());
- }
- });
- }
-
- private Wallet.SendRequest getSendRequest(String fromAddress,
- String toAddress,
- Coin amount,
- @Nullable KeyParameter aesKey,
- AddressEntry.Context context) throws AddressFormatException,
- AddressEntryException, InsufficientMoneyException {
- Transaction tx = new Transaction(params);
- Preconditions.checkArgument(Restrictions.isAboveDust(amount),
- "The amount is too low (dust limit).");
- tx.addOutput(amount, new Address(params, toAddress));
-
- Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
- sendRequest.aesKey = aesKey;
- sendRequest.shuffleOutputs = false;
- Optional addressEntry = findAddressEntry(fromAddress, context);
- if (!addressEntry.isPresent())
- throw new AddressEntryException("WithdrawFromAddress is not found in our wallet.");
-
- checkNotNull(addressEntry.get(), "addressEntry.get() must not be null");
- checkNotNull(addressEntry.get().getAddress(), "addressEntry.get().getAddress() must not be null");
- sendRequest.coinSelector = new TradeWalletCoinSelector(params, addressEntry.get().getAddress());
- sendRequest.changeAddress = addressEntry.get().getAddress();
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
- return sendRequest;
- }
-
- public int getTransactionSize(Set fromAddresses,
- String toAddress,
- Coin amount) throws
- AddressFormatException, AddressEntryException, InsufficientMoneyException {
- Wallet.SendRequest sendRequestForMultipleAddresses = getSendRequestForMultipleAddresses(fromAddresses, toAddress, amount, null, aesKey);
- Transaction tx = sendRequestForMultipleAddresses.tx;
- wallet.completeTx(sendRequestForMultipleAddresses);
- log.debug("No. of inputs: " + tx.getInputs().size());
- int size = tx.bitcoinSerialize().length;
- log.debug("Tx size: " + size);
- return size;
- }
-
- private Wallet.SendRequest getSendRequestForMultipleAddresses(Set fromAddresses,
- String toAddress,
- Coin amount,
- @Nullable String changeAddress,
- @Nullable KeyParameter aesKey) throws
- AddressFormatException, AddressEntryException, InsufficientMoneyException {
- Transaction tx = new Transaction(params);
- Preconditions.checkArgument(Restrictions.isAboveDust(amount),
- "The amount is too low (dust limit).");
- tx.addOutput(amount, new Address(params, toAddress));
-
- Wallet.SendRequest sendRequest = Wallet.SendRequest.forTx(tx);
- sendRequest.aesKey = aesKey;
- sendRequest.shuffleOutputs = false;
- Set addressEntries = fromAddresses.stream()
- .map(address -> {
- Optional addressEntryOptional = findAddressEntry(address, AddressEntry.Context.AVAILABLE);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.OFFER_FUNDING);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.TRADE_PAYOUT);
- if (!addressEntryOptional.isPresent())
- addressEntryOptional = findAddressEntry(address, AddressEntry.Context.ARBITRATOR);
- return addressEntryOptional;
- })
- .filter(Optional::isPresent)
- .map(Optional::get)
- .collect(Collectors.toSet());
- if (addressEntries.isEmpty())
- throw new AddressEntryException("No Addresses for withdraw found in our wallet");
-
- sendRequest.coinSelector = new MultiAddressesCoinSelector(params, addressEntries);
- Optional addressEntryOptional = Optional.empty();
- AddressEntry changeAddressAddressEntry = null;
- if (changeAddress != null)
- addressEntryOptional = findAddressEntry(changeAddress, AddressEntry.Context.AVAILABLE);
-
- if (addressEntryOptional.isPresent()) {
- changeAddressAddressEntry = addressEntryOptional.get();
- } else {
- ArrayList list = new ArrayList<>(addressEntries);
- if (!list.isEmpty())
- changeAddressAddressEntry = list.get(0);
- }
- checkNotNull(changeAddressAddressEntry, "change address must not be null");
- sendRequest.changeAddress = changeAddressAddressEntry.getAddress();
- sendRequest.feePerKb = FeePolicy.getNonTradeFeePerKb();
- return sendRequest;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Getters
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- public ReadOnlyDoubleProperty downloadPercentageProperty() {
- return downloadListener.percentageProperty();
- }
-
- public Wallet getWallet() {
- return wallet;
- }
-
- public Transaction getTransactionFromSerializedTx(byte[] tx) {
- return new Transaction(params, tx);
- }
-
- public ReadOnlyIntegerProperty numPeersProperty() {
- return numPeers;
- }
-
- public ReadOnlyObjectProperty> connectedPeersProperty() {
- return connectedPeers;
- }
-
-
- ///////////////////////////////////////////////////////////////////////////////////////////
- // Private methods
- ///////////////////////////////////////////////////////////////////////////////////////////
-
- private static void printTxWithInputs(String tracePrefix, Transaction tx) {
- log.trace(tracePrefix + ": " + tx.toString());
- for (TransactionInput input : tx.getInputs()) {
- if (input.getConnectedOutput() != null)
- log.trace(tracePrefix + " input value: " + input.getConnectedOutput().getValue().toFriendlyString());
- else
- log.trace(tracePrefix + ": Transaction already has inputs but we don't have the connected outputs, so we don't know the value.");
- }
- }
-
-
-///////////////////////////////////////////////////////////////////////////////////////////
-// Inner classes
-///////////////////////////////////////////////////////////////////////////////////////////
-
- private static class DownloadListener extends DownloadProgressTracker {
- private final DoubleProperty percentage = new SimpleDoubleProperty(-1);
-
- @Override
- protected void progress(double percentage, int blocksLeft, Date date) {
- super.progress(percentage, blocksLeft, date);
- UserThread.execute(() -> this.percentage.set(percentage / 100d));
- }
-
- @Override
- protected void doneDownload() {
- super.doneDownload();
- UserThread.execute(() -> this.percentage.set(1d));
- }
-
- public ReadOnlyDoubleProperty percentageProperty() {
- return percentage;
- }
- }
-
-
- private class BitsquareWalletEventListener extends AbstractWalletEventListener {
- @Override
- public void onCoinsReceived(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
- notifyBalanceListeners(tx);
- }
-
- @Override
- public void onCoinsSent(Wallet wallet, Transaction tx, Coin prevBalance, Coin newBalance) {
- notifyBalanceListeners(tx);
- }
-
- @Override
- public void onTransactionConfidenceChanged(Wallet wallet, Transaction tx) {
- for (AddressConfidenceListener addressConfidenceListener : addressConfidenceListeners) {
- List transactionConfidenceList = new ArrayList<>();
- transactionConfidenceList.add(getTransactionConfidence(tx, addressConfidenceListener.getAddress()));
-
- TransactionConfidence transactionConfidence = getMostRecentConfidence(transactionConfidenceList);
- addressConfidenceListener.onTransactionConfidenceChanged(transactionConfidence);
- }
-
- txConfidenceListeners.stream()
- .filter(txConfidenceListener -> tx != null &&
- tx.getHashAsString() != null &&
- txConfidenceListener != null &&
- tx.getHashAsString().equals(txConfidenceListener.getTxID()))
- .forEach(txConfidenceListener ->
- txConfidenceListener.onTransactionConfidenceChanged(tx.getConfidence()));
- }
-
- private void notifyBalanceListeners(Transaction tx) {
- for (BalanceListener balanceListener : balanceListeners) {
- Coin balance;
- if (balanceListener.getAddress() != null)
- balance = getBalanceForAddress(balanceListener.getAddress());
- else
- balance = getAvailableBalance();
-
- balanceListener.onBalanceChanged(balance, tx);
- }
- }
- }
-}
diff --git a/core/src/main/java/io/bitsquare/btc/wallet/WalletConfig.java b/core/src/main/java/io/bitsquare/btc/wallet/WalletConfig.java
index 7b63366326..6c4fa57819 100644
--- a/core/src/main/java/io/bitsquare/btc/wallet/WalletConfig.java
+++ b/core/src/main/java/io/bitsquare/btc/wallet/WalletConfig.java
@@ -99,10 +99,22 @@ public class WalletConfig extends AbstractIdleService {
/**
* Creates a new WalletAppKitBitSquare, with a newly created {@link Context}. Files will be stored in the given directory.
*/
- public WalletConfig(NetworkParameters params, Socks5Proxy socks5Proxy, File directory, String btcWalletFilePrefix, String squWalletFilePrefix) {
- this(new Context(params), directory, btcWalletFilePrefix, squWalletFilePrefix);
+ public WalletConfig(NetworkParameters params, Socks5Proxy socks5Proxy,
+ File directory, String btcWalletFilePrefix,
+ String squWalletFilePrefix) {
+ this.context = new Context(params);
+ this.params = checkNotNull(context.getParams());
+ this.directory = checkNotNull(directory);
+ this.btcWalletFilePrefix = checkNotNull(btcWalletFilePrefix);
+ this.squWalletFilePrefix = squWalletFilePrefix;
this.socks5Proxy = socks5Proxy;
+ if (!Utils.isAndroidRuntime()) {
+ InputStream stream = WalletConfig.class.getResourceAsStream("/" + params.getId() + ".checkpoints");
+ if (stream != null)
+ setCheckpoints(stream);
+ }
+
walletFactory = new BitsquareWalletFactory() {
@Override
public Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup) {
@@ -132,29 +144,6 @@ public class WalletConfig extends AbstractIdleService {
Wallet create(NetworkParameters params, KeyChainGroup keyChainGroup, boolean isSquWallet);
}
- /**
- * Creates a new WalletAppKitBitSquare, with a newly created {@link Context}. Files will be stored in the given directory.
- */
- private WalletConfig(NetworkParameters params, File directory, String btcWalletFilePrefix, String squWalletFilePrefix) {
- this(new Context(params), directory, btcWalletFilePrefix, squWalletFilePrefix);
- }
-
- /**
- * Creates a new WalletAppKitBitSquare, with the given {@link Context}. Files will be stored in the given directory.
- */
- private WalletConfig(Context context, File directory, String btcWalletFilePrefix, String squWalletFilePrefix) {
- this.context = context;
- this.params = checkNotNull(context.getParams());
- this.directory = checkNotNull(directory);
- this.btcWalletFilePrefix = checkNotNull(btcWalletFilePrefix);
- this.squWalletFilePrefix = squWalletFilePrefix;
- if (!Utils.isAndroidRuntime()) {
- InputStream stream = WalletConfig.class.getResourceAsStream("/" + params.getId() + ".checkpoints");
- if (stream != null)
- setCheckpoints(stream);
- }
- }
-
public Socks5Proxy getProxy() {
return socks5Proxy;
}
diff --git a/core/src/main/java/io/bitsquare/btc/wallet/WalletsSetup.java b/core/src/main/java/io/bitsquare/btc/wallet/WalletsSetup.java
index 345d51e8a8..b5fd5b59c5 100644
--- a/core/src/main/java/io/bitsquare/btc/wallet/WalletsSetup.java
+++ b/core/src/main/java/io/bitsquare/btc/wallet/WalletsSetup.java
@@ -27,13 +27,16 @@ import io.bitsquare.common.Timer;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.handlers.ExceptionHandler;
import io.bitsquare.common.handlers.ResultHandler;
+import io.bitsquare.network.DnsLookupTor;
+import io.bitsquare.network.NetworkOptionKeys;
+import io.bitsquare.network.Socks5MultiDiscovery;
import io.bitsquare.network.Socks5ProxyProvider;
import io.bitsquare.storage.FileUtil;
import io.bitsquare.storage.Storage;
import io.bitsquare.user.Preferences;
import javafx.beans.property.*;
+import org.apache.commons.lang3.StringUtils;
import org.bitcoinj.core.*;
-import org.bitcoinj.net.discovery.SeedPeers;
import org.bitcoinj.params.MainNetParams;
import org.bitcoinj.params.RegTestParams;
import org.bitcoinj.params.TestNet3Params;
@@ -71,6 +74,7 @@ public class WalletsSetup {
private final Socks5ProxyProvider socks5ProxyProvider;
private final NetworkParameters params;
private final File walletDir;
+ private final int socks5DiscoverMode;
private WalletConfig walletConfig;
private Wallet btcWallet;
private Wallet squWallet;
@@ -93,7 +97,8 @@ public class WalletsSetup {
UserAgent userAgent,
Preferences preferences,
Socks5ProxyProvider socks5ProxyProvider,
- @Named(BtcOptionKeys.WALLET_DIR) File appDir) {
+ @Named(BtcOptionKeys.WALLET_DIR) File appDir,
+ @Named(NetworkOptionKeys.SOCKS5_DISCOVER_MODE) String socks5DiscoverModeString) {
this.regTestHost = regTestHost;
this.addressEntryList = addressEntryList;
@@ -101,6 +106,27 @@ public class WalletsSetup {
this.preferences = preferences;
this.socks5ProxyProvider = socks5ProxyProvider;
+ String[] socks5DiscoverModes = StringUtils.deleteWhitespace(socks5DiscoverModeString).split(",");
+ int mode = 0;
+ for (int i = 0; i < socks5DiscoverModes.length; i++) {
+ switch (socks5DiscoverModes[i]) {
+ case "ADDR":
+ mode |= Socks5MultiDiscovery.SOCKS5_DISCOVER_ADDR;
+ break;
+ case "DNS":
+ mode |= Socks5MultiDiscovery.SOCKS5_DISCOVER_DNS;
+ break;
+ case "ONION":
+ mode |= Socks5MultiDiscovery.SOCKS5_DISCOVER_ONION;
+ break;
+ case "ALL":
+ default:
+ mode |= Socks5MultiDiscovery.SOCKS5_DISCOVER_ALL;
+ break;
+ }
+ }
+ socks5DiscoverMode = mode;
+
params = preferences.getBitcoinNetwork().getParameters();
walletDir = new File(appDir, "bitcoin");
@@ -247,9 +273,7 @@ public class WalletsSetup {
// Pass custom seed nodes if set in options
if (!btcNodes.isEmpty()) {
- // TODO: this parsing should be more robust,
- // give validation error if needed.
- String[] nodes = btcNodes.replace(", ", ",").split(",");
+ String[] nodes = StringUtils.deleteWhitespace(btcNodes).split(",");
List peerAddressList = new ArrayList<>();
for (String node : nodes) {
String[] parts = node.split(":");
@@ -260,27 +284,31 @@ public class WalletsSetup {
if (parts.length == 2) {
// note: this will cause a DNS request if hostname used.
// note: DNS requests are routed over socks5 proxy, if used.
- // fixme: .onion hostnames will fail! see comments in SeedPeersSocks5Dns
+ // note: .onion hostnames will be unresolved.
InetSocketAddress addr;
if (socks5Proxy != null) {
- InetSocketAddress unresolved = InetSocketAddress.createUnresolved(parts[0], Integer.parseInt(parts[1]));
- // proxy remote DNS request happens here.
- addr = SeedPeersSocks5Dns.lookup(socks5Proxy, unresolved);
+ try {
+ // proxy remote DNS request happens here. blocking.
+ addr = new InetSocketAddress(DnsLookupTor.lookup(socks5Proxy, parts[0]), Integer.parseInt(parts[1]));
+ } catch (Exception e) {
+ log.warn("Dns lookup failed for host: {}", parts[0]);
+ addr = null;
+ }
} else {
// DNS request happens here. if it fails, addr.isUnresolved() == true.
addr = new InetSocketAddress(parts[0], Integer.parseInt(parts[1]));
}
- // note: isUnresolved check should be removed once we fix PeerAddress
- if (addr != null && !addr.isUnresolved())
+ if (addr != null && !addr.isUnresolved()) {
peerAddressList.add(new PeerAddress(addr.getAddress(), addr.getPort()));
+ }
}
- }
- if (peerAddressList.size() > 0) {
- PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
- log.debug("btcNodes parsed: " + Arrays.toString(peerAddressListFixed));
+ if (peerAddressList.size() > 0) {
+ PeerAddress peerAddressListFixed[] = new PeerAddress[peerAddressList.size()];
+ log.debug("btcNodes parsed: " + Arrays.toString(peerAddressListFixed));
- walletConfig.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
- usePeerNodes = true;
+ walletConfig.setPeerNodes(peerAddressList.toArray(peerAddressListFixed));
+ usePeerNodes = true;
+ }
}
}
@@ -324,9 +352,8 @@ public class WalletsSetup {
// could become outdated, so it is important that the user be able to
// disable it, but should be made aware of the reduced privacy.
if (socks5Proxy != null && !usePeerNodes) {
- // SeedPeersSocks5Dns should replace SeedPeers once working reliably.
// SeedPeers uses hard coded stable addresses (from MainNetParams). It should be updated from time to time.
- walletConfig.setDiscovery(new SeedPeers(params));
+ walletConfig.setDiscovery(new Socks5MultiDiscovery(socks5Proxy, params, socks5DiscoverMode));
}
walletConfig.setDownloadListener(downloadListener)
diff --git a/core/src/main/java/io/bitsquare/trade/offer/Offer.java b/core/src/main/java/io/bitsquare/trade/offer/Offer.java
index 6463877f7a..4711be95f0 100644
--- a/core/src/main/java/io/bitsquare/trade/offer/Offer.java
+++ b/core/src/main/java/io/bitsquare/trade/offer/Offer.java
@@ -422,6 +422,7 @@ public final class Offer implements StoragePayload, RequiresOwnerIsOnlinePayload
return pubKeyRing;
}
+ // TODO refactor
@Nullable
public Fiat getPrice() {
if (useMarketBasedPrice) {
diff --git a/gui/pom.xml b/gui/pom.xml
index a8feb6e91a..622c836e85 100644
--- a/gui/pom.xml
+++ b/gui/pom.xml
@@ -22,7 +22,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/gui/src/main/java/io/bitsquare/gui/main/market/spread/SpreadViewModel.java b/gui/src/main/java/io/bitsquare/gui/main/market/spread/SpreadViewModel.java
index f6ee0b5ba4..f26101bb0e 100644
--- a/gui/src/main/java/io/bitsquare/gui/main/market/spread/SpreadViewModel.java
+++ b/gui/src/main/java/io/bitsquare/gui/main/market/spread/SpreadViewModel.java
@@ -18,8 +18,8 @@
package io.bitsquare.gui.main.market.spread;
import com.google.inject.Inject;
-import io.bitsquare.btc.pricefeed.MarketPrice;
-import io.bitsquare.btc.pricefeed.PriceFeedService;
+import io.bitsquare.btc.provider.price.MarketPrice;
+import io.bitsquare.btc.provider.price.PriceFeedService;
import io.bitsquare.gui.common.model.ActivatableViewModel;
import io.bitsquare.gui.main.offer.offerbook.OfferBook;
import io.bitsquare.gui.main.offer.offerbook.OfferBookListItem;
diff --git a/headless/pom.xml b/headless/pom.xml
index 6f237eda43..19abbb09cb 100644
--- a/headless/pom.xml
+++ b/headless/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/jsocks/pom.xml b/jsocks/pom.xml
index 7d2bde325b..f9047fe813 100644
--- a/jsocks/pom.xml
+++ b/jsocks/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/jtorctl/pom.xml b/jtorctl/pom.xml
index e57edcaa65..e3f0a7aa25 100644
--- a/jtorctl/pom.xml
+++ b/jtorctl/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/jtorproxy/pom.xml b/jtorproxy/pom.xml
index 02432a1e54..bfeb37064d 100644
--- a/jtorproxy/pom.xml
+++ b/jtorproxy/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/monitor/pom.xml b/monitor/pom.xml
index 49b69524da..0500a4be2b 100644
--- a/monitor/pom.xml
+++ b/monitor/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/network/pom.xml b/network/pom.xml
index 251646cc62..b0d314fb7b 100644
--- a/network/pom.xml
+++ b/network/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/package/linux/32bitBuild.sh b/package/linux/32bitBuild.sh
index 3bc3be4fca..7a91d5371d 100644
--- a/package/linux/32bitBuild.sh
+++ b/package/linux/32bitBuild.sh
@@ -6,7 +6,7 @@ mkdir -p gui/deploy
set -e
# Edit version
-version=0.4.9.9
+version=0.5.0.0
jarFile="/media/sf_vm_shared_ubuntu14_32bit/Bitsquare-$version.jar"
diff --git a/package/linux/64bitBuild.sh b/package/linux/64bitBuild.sh
index d43e54f101..01bc902323 100644
--- a/package/linux/64bitBuild.sh
+++ b/package/linux/64bitBuild.sh
@@ -6,7 +6,7 @@ mkdir -p gui/deploy
set -e
# Edit version
-version=0.4.9.9
+version=0.5.0.0
jarFile="/media/sf_vm_shared_ubuntu/Bitsquare-$version.jar"
diff --git a/package/mac/finalize.sh b/package/mac/finalize.sh
index 96b7734089..ec50ccd5a2 100644
--- a/package/mac/finalize.sh
+++ b/package/mac/finalize.sh
@@ -1,6 +1,6 @@
#!/bin/bash
-version="0.4.9.9"
+version="0.5.0.0"
target_dir="/Users/dev/Documents/__bitsquare/_releases/$version"
src_dir="/Users/dev/Documents/intellij/bitsquare"
diff --git a/package/windows/32bitBuild.bat b/package/windows/32bitBuild.bat
index 0777980ced..40d7b3ab77 100644
--- a/package/windows/32bitBuild.bat
+++ b/package/windows/32bitBuild.bat
@@ -5,7 +5,7 @@
:: 32 bit build
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
-SET version=0.4.9.9
+SET version=0.5.0.0
:: Private setup
SET outdir=\\VBOXSVR\vm_shared_windows_32bit
diff --git a/package/windows/64bitBuild.bat b/package/windows/64bitBuild.bat
index b00f2b82c8..7f85a674af 100644
--- a/package/windows/64bitBuild.bat
+++ b/package/windows/64bitBuild.bat
@@ -5,7 +5,7 @@
:: 64 bit build
:: Needs Inno Setup 5 or later (http://www.jrsoftware.org/isdl.php)
-SET version=0.4.9.9
+SET version=0.5.0.0
:: Private setup
SET outdir=\\VBOXSVR\vm_shared_windows
diff --git a/package/windows/Bitsquare.iss b/package/windows/Bitsquare.iss
index fcf45fbe3c..3f556544f6 100755
--- a/package/windows/Bitsquare.iss
+++ b/package/windows/Bitsquare.iss
@@ -3,7 +3,7 @@
[Setup]
AppId={{bitsquare}}
AppName=Bitsquare
-AppVersion=0.4.9.9
+AppVersion=0.5.0.0
AppVerName=Bitsquare
AppPublisher=Bitsquare
AppComments=Bitsquare
diff --git a/pom.xml b/pom.xml
index d4a1796d2f..f581cc5b00 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
io.bitsquare
parent
pom
- 0.4.9.8
+ 0.5.0.0
Bitsquare - The decentralized bitcoin exchange
https://bitsquare.io
diff --git a/provider/pom.xml b/provider/pom.xml
index 4680bdbaf7..454e4878a8 100644
--- a/provider/pom.xml
+++ b/provider/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/provider/src/main/java/io/bitsquare/provider/ProviderMain.java b/provider/src/main/java/io/bitsquare/provider/ProviderMain.java
index a5496582ad..d3d3aa3b76 100644
--- a/provider/src/main/java/io/bitsquare/provider/ProviderMain.java
+++ b/provider/src/main/java/io/bitsquare/provider/ProviderMain.java
@@ -15,7 +15,7 @@
* along with Bitsquare. If not, see .
*/
-package io.bitsquare.pricefeed;
+package io.bitsquare.provider;
import ch.qos.logback.classic.Level;
import io.bitsquare.app.Log;
diff --git a/seednode/pom.xml b/seednode/pom.xml
index b92851b2c0..df225e113b 100644
--- a/seednode/pom.xml
+++ b/seednode/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0
diff --git a/statistics/pom.xml b/statistics/pom.xml
index ad108a69c1..219b954b85 100644
--- a/statistics/pom.xml
+++ b/statistics/pom.xml
@@ -5,7 +5,7 @@
parent
io.bitsquare
- 0.4.9.9
+ 0.5.0.0
4.0.0