Improve UI, add notification popups, fee warning,...

This commit is contained in:
Manfred Karrer 2016-02-08 20:09:08 +01:00
parent 9438bc818e
commit 1f8b1b0e01
50 changed files with 634 additions and 435 deletions

View file

@ -60,6 +60,11 @@ public class FeePolicy {
return FEE_PER_KB;
}
// Some wallets (Mycelium) don't support higher fees
public static Coin getMinFundingFee() {
return Coin.valueOf(20_000);
}
// 0.001 BTC 0.1% of 1 BTC about 0.4 EUR @ 400 EUR/BTC
public static Coin getCreateOfferFee() {

View file

@ -53,7 +53,6 @@ import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
@ -90,7 +89,6 @@ public class WalletService {
private final IntegerProperty numPeers = new SimpleIntegerProperty(0);
private final ObjectProperty<List<Peer>> connectedPeers = new SimpleObjectProperty<>();
public final BooleanProperty shutDownDone = new SimpleBooleanProperty();
private ArbitraryTransactionBloomFilter arbitraryTransactionBloomFilter;
///////////////////////////////////////////////////////////////////////////////////////////
@ -705,86 +703,4 @@ public class WalletService {
}
}
}
public void requestTransactionBlockchainProvider(Transaction transaction, Consumer<Coin> resultHandler) {
// TODO use http server over tor to request tx in question
// https://btc.blockr.io/api/v1/tx/info/9a0c37209a45a0e61a50a62fcb7d0f52f3d6ed41faaf0afc044d642ab541b675
}
public void requestTransactionFromBlockChain(Transaction transaction, Consumer<Coin> resultHandler) {
requestTransactionBlockchainProvider(transaction, resultHandler);
/* arbitraryTransactionBloomFilter = new ArbitraryTransactionBloomFilter(transaction, resultHandler);
PeerGroup peerGroup = walletAppKit.peerGroup();
peerGroup.addEventListener(arbitraryTransactionBloomFilter);
peerGroup.addPeerFilterProvider(arbitraryTransactionBloomFilter);
log.debug("transaction=" + transaction);
log.debug("transaction.fee=" + transaction.getFee());*/
}
private class ArbitraryTransactionBloomFilter extends AbstractPeerEventListener implements PeerFilterProvider {
private final Transaction transaction;
private final Consumer<Coin> resultHandler;
private final Set<TransactionOutPoint> transactionOutPoints;
public ArbitraryTransactionBloomFilter(Transaction transaction, Consumer<Coin> resultHandler) {
this.transaction = transaction;
this.resultHandler = resultHandler;
transactionOutPoints = transaction.getInputs().stream()
.map(e -> e.getOutpoint() != null ? e.getOutpoint() : null)
.filter(e -> e != null)
.collect(Collectors.toSet());
log.debug("transaction=" + transaction);
log.debug("transaction.fee=" + transaction.getFee());
log.debug("outpoints=" + transactionOutPoints);
}
@Override
public long getEarliestKeyCreationTime() {
return System.currentTimeMillis() / 1000;
}
@Override
public void beginBloomFilterCalculation() {
}
@Override
public int getBloomFilterElementCount() {
return transactionOutPoints.size();
}
@Override
public BloomFilter getBloomFilter(int size, double falsePositiveRate, long nTweak) {
BloomFilter filter = new BloomFilter(size, falsePositiveRate, nTweak);
for (TransactionOutPoint transactionOutPoint : transactionOutPoints) {
filter.insert(transactionOutPoint.bitcoinSerialize());
}
return filter;
}
@Override
public boolean isRequiringUpdateAllBloomFilter() {
return false;
}
@Override
public void endBloomFilterCalculation() {
}
@Override
public void onTransaction(Peer peer, Transaction tx) {
if (transactionOutPoints.contains(tx))
transactionOutPoints.remove(tx);
if (transactionOutPoints.isEmpty())
resultHandler.accept(transaction.getFee());
log.debug("## onTransaction: transaction=" + tx);
log.debug("## onTransaction: transaction.fee=" + tx.getFee());
}
}
}

View file

@ -0,0 +1,10 @@
package io.bitsquare.btc.http;
import org.bitcoinj.core.Coin;
import java.io.IOException;
import java.io.Serializable;
public interface BlockchainApiProvider extends Serializable {
Coin getFee(String transactionId) throws IOException, HttpException;
}

View file

@ -0,0 +1,44 @@
package io.bitsquare.btc.http;
import com.google.gson.JsonParser;
import io.bitsquare.app.Log;
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/");
}
@Override
public Coin getFee(String transactionId) throws IOException, HttpException {
Log.traceCall("transactionId=" + transactionId);
try {
return Coin.parseCoin(new JsonParser()
.parse(httpClient.requestWithGET(transactionId))
.getAsJsonObject()
.get("data")
.getAsJsonObject()
.get("fee")
.getAsString());
} catch (IOException | HttpException e) {
log.warn("Error at requesting transaction data from block explorer " + httpClient + "\n" +
"Error =" + e.getMessage());
throw e;
}
}
}

View file

@ -0,0 +1,52 @@
package io.bitsquare.btc.http;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
// TODO route over tor
public class HttpClient implements Serializable {
private String baseUrl;
public HttpClient(String baseUrl) {
this.baseUrl = baseUrl;
}
public String requestWithGET(String param) throws IOException, HttpException {
HttpURLConnection connection = null;
try {
URL url = new URL(baseUrl + param);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setConnectTimeout(10000);
if (connection.getResponseCode() == 200) {
return convertInputStreamToString(connection.getInputStream());
} else {
connection.getErrorStream().close();
throw new HttpException(convertInputStreamToString(connection.getErrorStream()));
}
} finally {
if (connection != null)
connection.getInputStream().close();
}
}
private String convertInputStreamToString(InputStream inputStream) throws IOException {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line);
}
return stringBuilder.toString();
}
@Override
public String toString() {
return "HttpClient{" +
"baseUrl='" + baseUrl + '\'' +
'}';
}
}

View file

@ -0,0 +1,7 @@
package io.bitsquare.btc.http;
public class HttpException extends Exception {
public HttpException(String message) {
super(message);
}
}

View file

@ -61,7 +61,7 @@ public class PaymentMethod implements Serializable, Comparable {
public static final List<PaymentMethod> ALL_VALUES = new ArrayList<>(Arrays.asList(
OK_PAY = new PaymentMethod(OK_PAY_ID, 0, DAY), // tx instant so min. wait time
PERFECT_MONEY = new PaymentMethod(PERFECT_MONEY_ID, 0, DAY),
SEPA = new PaymentMethod(SEPA_ID, 0, 7 * DAY), // sepa takes 1-3 business days. We use 7 days to include safety for holidays
SEPA = new PaymentMethod(SEPA_ID, 0, 8 * DAY), // sepa takes 1-3 business days. We use 8 days to include safety for holidays
SWISH = new PaymentMethod(SWISH_ID, 0, DAY),
ALI_PAY = new PaymentMethod(ALI_PAY_ID, 0, DAY),
/* FED_WIRE = new PaymentMethod(FED_WIRE_ID, 0, DAY),*/

View file

@ -147,6 +147,7 @@ abstract public class Trade implements Tradable, Model, Serializable {
// Mutable
private DecryptedMsgWithPubKey decryptedMsgWithPubKey;
private Date takeOfferDate = new Date(0); // in some error cases the date is not set and cause null pointers, so we set a default
private int takeOfferDateAsBlockHeight;
private Coin tradeAmount;
private NodeAddress tradingPeerNodeAddress;
protected State state;
@ -164,8 +165,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
private int checkPaymentTimeAsBlockHeight;
private NodeAddress arbitratorNodeAddress;
private String takerPaymentAccountId;
private boolean halfTradePeriodReachedWarningDisplayed;
private boolean tradePeriodOverWarningDisplayed;
private String errorMessage;
transient private StringProperty errorMessageProperty;
transient private ObjectProperty<Coin> tradeAmountProperty;
@ -418,6 +417,14 @@ abstract public class Trade implements Tradable, Model, Serializable {
this.takeOfferDate = takeOfferDate;
}
public int getTakeOfferDateAsBlockHeight() {
return takeOfferDateAsBlockHeight;
}
public void setTakeOfferDateAsBlockHeight(int blockHeight) {
takeOfferDateAsBlockHeight = blockHeight;
}
public void setTradingPeerNodeAddress(NodeAddress tradingPeerNodeAddress) {
if (tradingPeerNodeAddress == null)
log.error("tradingPeerAddress=null");
@ -536,24 +543,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
this.takerPaymentAccountId = takerPaymentAccountId;
}
public void setHalfTradePeriodReachedWarningDisplayed(boolean halfTradePeriodReachedWarningDisplayed) {
this.halfTradePeriodReachedWarningDisplayed = halfTradePeriodReachedWarningDisplayed;
persist();
}
public boolean isHalfTradePeriodReachedWarningDisplayed() {
return halfTradePeriodReachedWarningDisplayed;
}
public void setTradePeriodOverWarningDisplayed(boolean tradePeriodOverWarningDisplayed) {
this.tradePeriodOverWarningDisplayed = tradePeriodOverWarningDisplayed;
persist();
}
public boolean isTradePeriodOverWarningDisplayed() {
return tradePeriodOverWarningDisplayed;
}
public void setContractHash(byte[] contractHash) {
this.contractHash = contractHash;
}
@ -638,8 +627,6 @@ abstract public class Trade implements Tradable, Model, Serializable {
"\n\tcheckPaymentTimeAsBlockHeight=" + checkPaymentTimeAsBlockHeight +
"\n\tarbitratorNodeAddress=" + arbitratorNodeAddress +
"\n\ttakerPaymentAccountId='" + takerPaymentAccountId + '\'' +
"\n\thalfTradePeriodReachedWarningDisplayed=" + halfTradePeriodReachedWarningDisplayed +
"\n\ttradePeriodOverWarningDisplayed=" + tradePeriodOverWarningDisplayed +
"\n\terrorMessage='" + errorMessage + '\'' +
'}';
}

View file

@ -281,6 +281,7 @@ public class TradeManager {
trade = new BuyerAsTakerTrade(offer, amount, model.getPeerNodeAddress(), tradableListStorage);
trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(tradeWalletService.getBestChainHeight());
trade.setTakerPaymentAccountId(paymentAccountId);
initTrade(trade);
@ -382,8 +383,11 @@ public class TradeManager {
return offer.isMyOffer(keyRing);
}
public boolean isMyOfferInBtcBuyerRole(Offer offer) {
return !(isMyOffer(offer) ^ offer.getDirection() == Offer.Direction.BUY);
}
public Optional<Trade> getTradeById(String tradeId) {
return trades.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
}
}

View file

@ -133,10 +133,7 @@ public class BuyerAsOffererProtocol extends TradeProtocol implements BuyerProtoc
TradeTaskRunner taskRunner = new TradeTaskRunner(buyerAsOffererTrade,
() -> handleTaskRunnerSuccess("handle DepositTxPublishedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(
ProcessDepositTxPublishedMessage.class,
AddDepositTxToWallet.class
);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
taskRunner.run();
}

View file

@ -132,10 +132,7 @@ public class SellerAsOffererProtocol extends TradeProtocol implements SellerProt
() -> handleTaskRunnerSuccess("DepositTxPublishedMessage"),
this::handleTaskRunnerFault);
taskRunner.addTasks(
ProcessDepositTxPublishedMessage.class,
AddDepositTxToWallet.class
);
taskRunner.addTasks(ProcessDepositTxPublishedMessage.class);
taskRunner.run();
}

View file

@ -65,6 +65,7 @@ public class SignAndPublishDepositTxAsBuyer extends TradeTask {
trade.setDepositTx(transaction);
trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);
complete();

View file

@ -1,48 +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 <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class AddDepositTxToWallet extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(AddDepositTxToWallet.class);
public AddDepositTxToWallet(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// To access tx confidence we need to add that tx into our wallet.
Transaction depositTx = processModel.getTradeWalletService().addTransactionToWallet(trade.getDepositTx());
// update with full tx
trade.setDepositTx(depositTx);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -22,6 +22,7 @@ import io.bitsquare.trade.OffererTrade;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.protocol.trade.messages.DepositTxPublishedMessage;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.bitcoinj.core.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -47,9 +48,15 @@ public class ProcessDepositTxPublishedMessage extends TradeTask {
checkTradeId(processModel.getId(), message);
checkNotNull(message);
checkArgument(message.depositTx != null);
trade.setDepositTx(processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx));
// To access tx confidence we need to add that tx into our wallet.
Transaction transactionFromSerializedTx = processModel.getWalletService().getTransactionFromSerializedTx(message.depositTx);
// update with full tx
trade.setDepositTx(processModel.getTradeWalletService().addTransactionToWallet(transactionFromSerializedTx));
trade.setState(Trade.State.DEPOSIT_PUBLISHED_MSG_RECEIVED);
trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
if (trade instanceof OffererTrade)
processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer());

View file

@ -63,6 +63,7 @@ public class SignAndPublishDepositTxAsSeller extends TradeTask {
trade.setDepositTx(transaction);
trade.setTakeOfferDate(new Date());
trade.setTakeOfferDateAsBlockHeight(processModel.getTradeWalletService().getBestChainHeight());
trade.setState(Trade.State.DEPOSIT_PUBLISHED);
complete();

View file

@ -21,6 +21,8 @@ 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;
@ -65,12 +67,15 @@ 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<String> getBtcDenominations() {
return BTC_DENOMINATIONS;
}
private static Locale defaultLocale = Locale.getDefault();
//TODO test with other locales
//private static Locale defaultLocale = Locale.US;
public static Locale getDefaultLocale() {
return defaultLocale;
@ -91,7 +96,6 @@ public class Preferences implements Serializable {
private String btcDenomination = MonetaryFormat.CODE_BTC;
private boolean useAnimations = true;
private boolean useEffects = true;
private boolean displaySecurityDepositInfo = true;
private final ArrayList<TradeCurrency> tradeCurrencies;
private BlockChainExplorer blockChainExplorerMainNet;
private BlockChainExplorer blockChainExplorerTestNet;
@ -130,7 +134,6 @@ public class Preferences implements Serializable {
setUseEffects(persisted.useEffects);
setTradeCurrencies(persisted.tradeCurrencies);
tradeCurrencies = new ArrayList<>(tradeCurrenciesAsObservable);
displaySecurityDepositInfo = persisted.getDisplaySecurityDepositInfo();
setBlockChainExplorerTestNet(persisted.getBlockChainExplorerTestNet());
setBlockChainExplorerMainNet(persisted.getBlockChainExplorerMainNet());
@ -152,6 +155,8 @@ public class Preferences implements Serializable {
preferredTradeCurrency = persisted.getPreferredTradeCurrency();
defaultTradeCurrency = preferredTradeCurrency;
useTorForBitcoinJ = persisted.getUseTorForBitcoinJ();
blockchainApiProvider = persisted.getBlockchainApiProvider();
try {
setTxFeePerKB(persisted.getTxFeePerKB());
@ -175,6 +180,8 @@ public class Preferences implements Serializable {
preferredLocale = getDefaultLocale();
preferredTradeCurrency = getDefaultTradeCurrency();
blockchainApiProvider = new BlockrIOProvider();
storage.queueUpForSave();
}
@ -223,11 +230,6 @@ public class Preferences implements Serializable {
this.useEffectsProperty.set(useEffectsProperty);
}
public void setDisplaySecurityDepositInfo(boolean displaySecurityDepositInfo) {
this.displaySecurityDepositInfo = displaySecurityDepositInfo;
storage.queueUpForSave(2000);
}
public void setBitcoinNetwork(BitcoinNetwork bitcoinNetwork) {
if (this.bitcoinNetwork != bitcoinNetwork)
bitsquareEnvironment.saveBitcoinNetwork(bitcoinNetwork);
@ -302,6 +304,10 @@ public class Preferences implements Serializable {
storage.queueUpForSave();
}
public void setBlockchainApiProvider(BlockchainApiProvider blockchainApiProvider) {
this.blockchainApiProvider = blockchainApiProvider;
storage.queueUpForSave();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getter
@ -319,10 +325,6 @@ public class Preferences implements Serializable {
return useAnimationsProperty.get();
}
public boolean getDisplaySecurityDepositInfo() {
return displaySecurityDepositInfo;
}
public StringProperty btcDenominationProperty() {
return btcDenominationProperty;
}
@ -425,4 +427,9 @@ public class Preferences implements Serializable {
public boolean getUseTorForBitcoinJ() {
return useTorForBitcoinJ;
}
public BlockchainApiProvider getBlockchainApiProvider() {
return blockchainApiProvider;
}
}