From 96090b71ad79fed98f1f293695f0881947af27d2 Mon Sep 17 00:00:00 2001 From: Manfred Karrer Date: Mon, 8 Feb 2016 22:42:52 +0100 Subject: [PATCH] Ad more blockchain providers, use try-again-on-failure --- .../java/io/bitsquare/btc/BitcoinModule.java | 2 + .../btc/blockchain/BlockchainService.java | 54 +++++++++++++ .../btc/blockchain/GetFeeRequest.java | 77 +++++++++++++++++++ .../btc/{http => blockchain}/HttpClient.java | 2 +- .../{http => blockchain}/HttpException.java | 2 +- .../providers/BlockTrailProvider.java | 45 +++++++++++ .../providers}/BlockchainApiProvider.java | 3 +- .../providers}/BlockrIOProvider.java | 25 +++--- .../providers/TradeBlockProvider.java | 47 +++++++++++ .../java/io/bitsquare/user/Preferences.java | 15 ---- .../btc/blockchain/BlockchainServiceTest.java | 37 +++++++++ .../createoffer/CreateOfferDataModel.java | 37 +++++---- 12 files changed, 300 insertions(+), 46 deletions(-) create mode 100644 core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java create mode 100644 core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java rename core/src/main/java/io/bitsquare/btc/{http => blockchain}/HttpClient.java (97%) rename core/src/main/java/io/bitsquare/btc/{http => blockchain}/HttpException.java (76%) create mode 100644 core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java rename core/src/main/java/io/bitsquare/btc/{http => blockchain/providers}/BlockchainApiProvider.java (70%) rename core/src/main/java/io/bitsquare/btc/{http => blockchain/providers}/BlockrIOProvider.java (65%) create mode 100644 core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java create mode 100644 core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java diff --git a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java index 63b6e6aaa6..1347f4a071 100644 --- a/core/src/main/java/io/bitsquare/btc/BitcoinModule.java +++ b/core/src/main/java/io/bitsquare/btc/BitcoinModule.java @@ -19,6 +19,7 @@ package io.bitsquare.btc; import com.google.inject.Singleton; import io.bitsquare.app.AppModule; +import io.bitsquare.btc.blockchain.BlockchainService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; @@ -49,6 +50,7 @@ public class BitcoinModule extends AppModule { bind(AddressEntryList.class).in(Singleton.class); bind(TradeWalletService.class).in(Singleton.class); bind(WalletService.class).in(Singleton.class); + bind(BlockchainService.class).in(Singleton.class); } } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java b/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java new file mode 100644 index 0000000000..3c335bfaaf --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/blockchain/BlockchainService.java @@ -0,0 +1,54 @@ +package io.bitsquare.btc.blockchain; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; +import com.google.inject.Inject; +import io.bitsquare.btc.blockchain.providers.BlockTrailProvider; +import io.bitsquare.btc.blockchain.providers.BlockchainApiProvider; +import io.bitsquare.btc.blockchain.providers.BlockrIOProvider; +import io.bitsquare.btc.blockchain.providers.TradeBlockProvider; +import org.bitcoinj.core.Coin; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; + +public class BlockchainService { + private static final Logger log = LoggerFactory.getLogger(BlockchainService.class); + + private final ArrayList providers; + + @Inject + public BlockchainService() { + providers = new ArrayList<>(Arrays.asList(new BlockrIOProvider(), new BlockTrailProvider(), new TradeBlockProvider())); + } + + public SettableFuture requestFeeFromBlockchain(String transactionId) { + log.debug("Request fee from providers"); + long startTime = System.currentTimeMillis(); + final SettableFuture resultFuture = SettableFuture.create(); + for (BlockchainApiProvider provider : providers) { + GetFeeRequest getFeeRequest = new GetFeeRequest(); + SettableFuture future = getFeeRequest.requestFee(transactionId, provider); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + if (!resultFuture.isDone()) { + log.info("Request fee from providers done after {} ms.", (System.currentTimeMillis() - startTime)); + resultFuture.set(fee); + } + } + + public void onFailure(@NotNull Throwable throwable) { + if (!resultFuture.isDone()) { + log.warn("Could not get the fee from any provider after repeated requests."); + resultFuture.setException(throwable); + } + } + }); + } + return resultFuture; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java b/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java new file mode 100644 index 0000000000..b333e84e52 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/blockchain/GetFeeRequest.java @@ -0,0 +1,77 @@ +package io.bitsquare.btc.blockchain; + +import com.google.common.util.concurrent.*; +import io.bitsquare.btc.blockchain.providers.BlockchainApiProvider; +import io.bitsquare.common.UserThread; +import io.bitsquare.common.util.Utilities; +import org.bitcoinj.core.Coin; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Timer; + +public class GetFeeRequest { + private static final Logger log = LoggerFactory.getLogger(GetFeeRequest.class); + private final ListeningExecutorService executorService; + private Timer timer; + private int faults; + + public GetFeeRequest() { + executorService = Utilities.getListeningExecutorService("GetFeeRequest", 5, 10, 120L); + } + + public SettableFuture requestFee(String transactionId, BlockchainApiProvider provider) { + final SettableFuture resultFuture = SettableFuture.create(); + return requestFee(transactionId, provider, resultFuture); + } + + private SettableFuture requestFee(String transactionId, BlockchainApiProvider provider, SettableFuture resultFuture) { + ListenableFuture future = executorService.submit(() -> { + Thread.currentThread().setName("requestFee-" + provider.toString()); + try { + return provider.getFee(transactionId); + } catch (IOException | HttpException e) { + log.warn("Fee request failed for tx {} from provider {}\n error={}", + transactionId, provider, e.getMessage()); + throw e; + } + }); + + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + log.info("Received fee of {}\nfor tx {}\nfrom provider {}", fee.toFriendlyString(), transactionId, provider); + resultFuture.set(fee); + } + + public void onFailure(@NotNull Throwable throwable) { + if (timer == null) { + timer = UserThread.runAfter(() -> { + stopTimer(); + faults++; + if (!resultFuture.isDone()) { + if (faults < 4) { + requestFee(transactionId, provider, resultFuture); + } else { + resultFuture.setException(throwable); + } + } else { + log.debug("Got an error after a successful result. " + + "That might happen when we get a delayed response from a timer request."); + } + }, 1 + faults); + } else { + log.warn("Timer was not null"); + } + } + }); + + return resultFuture; + } + + private void stopTimer() { + timer.cancel(); + timer = null; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/http/HttpClient.java b/core/src/main/java/io/bitsquare/btc/blockchain/HttpClient.java similarity index 97% rename from core/src/main/java/io/bitsquare/btc/http/HttpClient.java rename to core/src/main/java/io/bitsquare/btc/blockchain/HttpClient.java index a8b3b99bfc..821b57deea 100644 --- a/core/src/main/java/io/bitsquare/btc/http/HttpClient.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/HttpClient.java @@ -1,4 +1,4 @@ -package io.bitsquare.btc.http; +package io.bitsquare.btc.blockchain; import java.io.*; import java.net.HttpURLConnection; diff --git a/core/src/main/java/io/bitsquare/btc/http/HttpException.java b/core/src/main/java/io/bitsquare/btc/blockchain/HttpException.java similarity index 76% rename from core/src/main/java/io/bitsquare/btc/http/HttpException.java rename to core/src/main/java/io/bitsquare/btc/blockchain/HttpException.java index 541e515cc6..62b43a4916 100644 --- a/core/src/main/java/io/bitsquare/btc/http/HttpException.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/HttpException.java @@ -1,4 +1,4 @@ -package io.bitsquare.btc.http; +package io.bitsquare.btc.blockchain; public class HttpException extends Exception { public HttpException(String message) { diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java new file mode 100644 index 0000000000..039f3f3fad --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockTrailProvider.java @@ -0,0 +1,45 @@ +package io.bitsquare.btc.blockchain.providers; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.bitsquare.app.Log; +import io.bitsquare.btc.blockchain.HttpClient; +import io.bitsquare.btc.blockchain.HttpException; +import org.bitcoinj.core.Coin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class BlockTrailProvider implements BlockchainApiProvider { + private static final Logger log = LoggerFactory.getLogger(BlockTrailProvider.class); + + private final HttpClient httpClient; + + public BlockTrailProvider() { + httpClient = new HttpClient("https://www.blocktrail.com/BTC/json/blockchain/tx/"); + } + + @Override + public Coin getFee(String transactionId) throws IOException, HttpException { + Log.traceCall("transactionId=" + transactionId); + try { + JsonObject asJsonObject = new JsonParser() + .parse(httpClient.requestWithGET(transactionId)) + .getAsJsonObject(); + return Coin.valueOf(asJsonObject + .get("fee") + .getAsLong()); + } catch (IOException | HttpException e) { + log.debug("Error at requesting transaction data from block explorer " + httpClient + "\n" + + "Error =" + e.getMessage()); + throw e; + } + } + + @Override + public String toString() { + return "BlockTrailProvider{" + + '}'; + } +} diff --git a/core/src/main/java/io/bitsquare/btc/http/BlockchainApiProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java similarity index 70% rename from core/src/main/java/io/bitsquare/btc/http/BlockchainApiProvider.java rename to core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java index e0f01c0155..948caaf7a3 100644 --- a/core/src/main/java/io/bitsquare/btc/http/BlockchainApiProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockchainApiProvider.java @@ -1,5 +1,6 @@ -package io.bitsquare.btc.http; +package io.bitsquare.btc.blockchain.providers; +import io.bitsquare.btc.blockchain.HttpException; import org.bitcoinj.core.Coin; import java.io.IOException; diff --git a/core/src/main/java/io/bitsquare/btc/http/BlockrIOProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java similarity index 65% rename from core/src/main/java/io/bitsquare/btc/http/BlockrIOProvider.java rename to core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java index 159f1ef0b2..e2f2412730 100644 --- a/core/src/main/java/io/bitsquare/btc/http/BlockrIOProvider.java +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/BlockrIOProvider.java @@ -1,25 +1,21 @@ -package io.bitsquare.btc.http; +package io.bitsquare.btc.blockchain.providers; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.bitsquare.app.Log; +import io.bitsquare.btc.blockchain.HttpClient; +import io.bitsquare.btc.blockchain.HttpException; import org.bitcoinj.core.Coin; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; -// TODO route over tor, support several providers public class BlockrIOProvider implements BlockchainApiProvider { private static final Logger log = LoggerFactory.getLogger(BlockrIOProvider.class); private final HttpClient httpClient; - public static void main(String[] args) throws HttpException, IOException { - Coin fee = new BlockrIOProvider() - .getFee("df67414652722d38b43dcbcac6927c97626a65bd4e76a2e2787e22948a7c5c47"); - log.debug("fee " + fee.toFriendlyString()); - } - public BlockrIOProvider() { httpClient = new HttpClient("https://btc.blockr.io/api/v1/tx/info/"); } @@ -28,17 +24,24 @@ public class BlockrIOProvider implements BlockchainApiProvider { public Coin getFee(String transactionId) throws IOException, HttpException { Log.traceCall("transactionId=" + transactionId); try { - return Coin.parseCoin(new JsonParser() + JsonObject data = new JsonParser() .parse(httpClient.requestWithGET(transactionId)) .getAsJsonObject() .get("data") - .getAsJsonObject() + .getAsJsonObject(); + return Coin.parseCoin(data .get("fee") .getAsString()); } catch (IOException | HttpException e) { - log.warn("Error at requesting transaction data from block explorer " + httpClient + "\n" + + log.debug("Error at requesting transaction data from block explorer " + httpClient + "\n" + "Error =" + e.getMessage()); throw e; } } + + @Override + public String toString() { + return "BlockrIOProvider{" + + '}'; + } } diff --git a/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java b/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java new file mode 100644 index 0000000000..4bdde84384 --- /dev/null +++ b/core/src/main/java/io/bitsquare/btc/blockchain/providers/TradeBlockProvider.java @@ -0,0 +1,47 @@ +package io.bitsquare.btc.blockchain.providers; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.bitsquare.app.Log; +import io.bitsquare.btc.blockchain.HttpClient; +import io.bitsquare.btc.blockchain.HttpException; +import org.bitcoinj.core.Coin; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class TradeBlockProvider implements BlockchainApiProvider { + private static final Logger log = LoggerFactory.getLogger(TradeBlockProvider.class); + + private final HttpClient httpClient; + + public TradeBlockProvider() { + httpClient = new HttpClient("https://tradeblock.com/api/blockchain/tx/"); + } + + @Override + public Coin getFee(String transactionId) throws IOException, HttpException { + Log.traceCall("transactionId=" + transactionId); + try { + JsonObject asJsonObject = new JsonParser() + .parse(httpClient.requestWithGET(transactionId)) + .getAsJsonObject(); + return Coin.valueOf(asJsonObject + .get("data") + .getAsJsonObject() + .get("fee") + .getAsLong()); + } catch (IOException | HttpException e) { + log.debug("Error at requesting transaction data from block explorer " + httpClient + "\n" + + "Error =" + e.getMessage()); + throw e; + } + } + + @Override + public String toString() { + return "TradeBlockProvider{" + + '}'; + } +} diff --git a/core/src/main/java/io/bitsquare/user/Preferences.java b/core/src/main/java/io/bitsquare/user/Preferences.java index 33d435f222..9b256e64f4 100644 --- a/core/src/main/java/io/bitsquare/user/Preferences.java +++ b/core/src/main/java/io/bitsquare/user/Preferences.java @@ -21,8 +21,6 @@ import io.bitsquare.app.BitsquareEnvironment; import io.bitsquare.app.Version; import io.bitsquare.btc.BitcoinNetwork; import io.bitsquare.btc.FeePolicy; -import io.bitsquare.btc.http.BlockchainApiProvider; -import io.bitsquare.btc.http.BlockrIOProvider; import io.bitsquare.locale.CountryUtil; import io.bitsquare.locale.CurrencyUtil; import io.bitsquare.locale.TradeCurrency; @@ -67,7 +65,6 @@ public class Preferences implements Serializable { new BlockChainExplorer("Blockr.io", "https://btc.blockr.io/tx/info/", "https://btc.blockr.io/address/info/"), new BlockChainExplorer("Biteasy", "https://www.biteasy.com/transactions/", "https://www.biteasy.com/addresses/") )); - private BlockchainApiProvider blockchainApiProvider; public static List getBtcDenominations() { return BTC_DENOMINATIONS; @@ -156,8 +153,6 @@ public class Preferences implements Serializable { defaultTradeCurrency = preferredTradeCurrency; useTorForBitcoinJ = persisted.getUseTorForBitcoinJ(); - blockchainApiProvider = persisted.getBlockchainApiProvider(); - try { setTxFeePerKB(persisted.getTxFeePerKB()); } catch (Exception e) { @@ -180,8 +175,6 @@ public class Preferences implements Serializable { preferredLocale = getDefaultLocale(); preferredTradeCurrency = getDefaultTradeCurrency(); - blockchainApiProvider = new BlockrIOProvider(); - storage.queueUpForSave(); } @@ -304,10 +297,6 @@ public class Preferences implements Serializable { storage.queueUpForSave(); } - public void setBlockchainApiProvider(BlockchainApiProvider blockchainApiProvider) { - this.blockchainApiProvider = blockchainApiProvider; - storage.queueUpForSave(); - } /////////////////////////////////////////////////////////////////////////////////////////// // Getter @@ -428,8 +417,4 @@ public class Preferences implements Serializable { return useTorForBitcoinJ; } - public BlockchainApiProvider getBlockchainApiProvider() { - return blockchainApiProvider; - } - } diff --git a/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java b/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java new file mode 100644 index 0000000000..e0354b6aac --- /dev/null +++ b/core/src/test/java/io/bitsquare/btc/blockchain/BlockchainServiceTest.java @@ -0,0 +1,37 @@ +package io.bitsquare.btc.blockchain; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; +import org.bitcoinj.core.Coin; +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static junit.framework.TestCase.assertTrue; + +public class BlockchainServiceTest { + private static final Logger log = LoggerFactory.getLogger(BlockchainServiceTest.class); + + @Test + public void testIsMinSpendableAmount() throws InterruptedException { + BlockchainService blockchainService = new BlockchainService(); + + // that tx has 0.001 BTC as fee + String transactionId = "38d176d0b1079b99fcb59859401d6b1679d2fa18fd8989d2c244b3682e52fce6"; + + SettableFuture future = blockchainService.requestFeeFromBlockchain(transactionId); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + log.debug(fee.toFriendlyString()); + assertTrue(fee.equals(Coin.MILLICOIN)); + } + + public void onFailure(@NotNull Throwable throwable) { + log.error(throwable.getMessage()); + } + }); + Thread.sleep(5000); + } +} diff --git a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java index b6eefa144d..9afc4b5149 100644 --- a/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java +++ b/gui/src/main/java/io/bitsquare/gui/main/offer/createoffer/CreateOfferDataModel.java @@ -17,17 +17,21 @@ package io.bitsquare.gui.main.offer.createoffer; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.SettableFuture; import com.google.inject.Inject; import io.bitsquare.arbitration.Arbitrator; import io.bitsquare.btc.AddressEntry; import io.bitsquare.btc.FeePolicy; import io.bitsquare.btc.TradeWalletService; import io.bitsquare.btc.WalletService; -import io.bitsquare.btc.http.HttpException; +import io.bitsquare.btc.blockchain.BlockchainService; import io.bitsquare.btc.listeners.BalanceListener; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.KeyRing; import io.bitsquare.gui.common.model.ActivatableDataModel; +import io.bitsquare.gui.popups.Popup; import io.bitsquare.gui.popups.WalletPasswordPopup; import io.bitsquare.gui.util.BSFormatter; import io.bitsquare.locale.Country; @@ -50,7 +54,6 @@ import org.bitcoinj.utils.ExchangeRate; import org.bitcoinj.utils.Fiat; import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.util.List; import java.util.UUID; @@ -70,6 +73,7 @@ class CreateOfferDataModel extends ActivatableDataModel { private final KeyRing keyRing; private final P2PService p2PService; private final WalletPasswordPopup walletPasswordPopup; + private BlockchainService blockchainService; private final BSFormatter formatter; private final String offerId; private final AddressEntry addressEntry; @@ -99,7 +103,6 @@ class CreateOfferDataModel extends ActivatableDataModel { final ObservableList paymentAccounts = FXCollections.observableArrayList(); private PaymentAccount paymentAccount; - private int retryRequestFeeCounter = 0; /////////////////////////////////////////////////////////////////////////////////////////// @@ -109,7 +112,7 @@ class CreateOfferDataModel extends ActivatableDataModel { @Inject CreateOfferDataModel(OpenOfferManager openOfferManager, WalletService walletService, TradeWalletService tradeWalletService, Preferences preferences, User user, KeyRing keyRing, P2PService p2PService, - WalletPasswordPopup walletPasswordPopup, BSFormatter formatter) { + WalletPasswordPopup walletPasswordPopup, BlockchainService blockchainService, BSFormatter formatter) { this.openOfferManager = openOfferManager; this.walletService = walletService; this.tradeWalletService = tradeWalletService; @@ -118,6 +121,7 @@ class CreateOfferDataModel extends ActivatableDataModel { this.keyRing = keyRing; this.p2PService = p2PService; this.walletPasswordPopup = walletPasswordPopup; + this.blockchainService = blockchainService; this.formatter = formatter; offerId = UUID.randomUUID().toString(); @@ -164,9 +168,7 @@ class CreateOfferDataModel extends ActivatableDataModel { } boolean isFeeFromFundingTxSufficient() { - // if fee was never set because of api provider not available we check with default value and return true - return feeFromFundingTxProperty.get().equals(Coin.NEGATIVE_SATOSHI) || - feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinFundingFee()) >= 0; + return feeFromFundingTxProperty.get().compareTo(FeePolicy.getMinFundingFee()) >= 0; } private void addListeners() { @@ -205,17 +207,18 @@ class CreateOfferDataModel extends ActivatableDataModel { } private void requestFeeFromBlockchain(String transactionId) { - try { - feeFromFundingTxProperty.set(preferences.getBlockchainApiProvider().getFee(transactionId)); - } catch (IOException | HttpException e) { - log.warn("Could not get fee from block explorer" + e); - if (retryRequestFeeCounter < 3) { - retryRequestFeeCounter++; - log.warn("We try again after 5 seconds"); - // TODO if we have more providers, try another one - UserThread.runAfter(() -> requestFeeFromBlockchain(transactionId), 5); + SettableFuture future = blockchainService.requestFeeFromBlockchain(transactionId); + Futures.addCallback(future, new FutureCallback() { + public void onSuccess(Coin fee) { + UserThread.execute(() -> feeFromFundingTxProperty.set(fee)); } - } + + public void onFailure(@NotNull Throwable throwable) { + UserThread.execute(() -> new Popup() + .warning("We did not get a result for the mining fee used in the funding transaction.") + .show()); + } + }); }