Add publish statistics task for taker if offerer uses old version

This commit is contained in:
Manfred Karrer 2016-07-25 16:13:26 +02:00
parent 38cc71c6aa
commit ada3bd4dc3
17 changed files with 213 additions and 49 deletions

View file

@ -10,12 +10,14 @@ import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import javax.annotation.concurrent.Immutable;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Immutable
public final class TradeStatistics implements StoragePayload, CapabilityRequiringPayload {
@JsonExclude
private static final long serialVersionUID = Version.P2P_NETWORK_VERSION;
@ -86,9 +88,72 @@ public final class TradeStatistics implements StoragePayload, CapabilityRequirin
}
public Fiat getTradeVolume() {
if (getTradeAmount() != null && getTradePrice() != null)
return new ExchangeRate(getTradePrice()).coinToFiat(getTradeAmount());
else
return null;
return new ExchangeRate(getTradePrice()).coinToFiat(getTradeAmount());
}
// We don't include the pubKeyRing as both traders might publish it if the offerer uses an old
// version and update later (taker publishes first, then later offerer)
// We also don't include the trade date as that is set locally and different for offerer and taker
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TradeStatistics)) return false;
TradeStatistics that = (TradeStatistics) o;
if (tradePrice != that.tradePrice) return false;
if (tradeAmount != that.tradeAmount) return false;
if (offerDate != that.offerDate) return false;
if (useMarketBasedPrice != that.useMarketBasedPrice) return false;
if (Double.compare(that.marketPriceMargin, marketPriceMargin) != 0) return false;
if (offerAmount != that.offerAmount) return false;
if (offerMinAmount != that.offerMinAmount) return false;
if (currency != null ? !currency.equals(that.currency) : that.currency != null) return false;
if (direction != that.direction) return false;
if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null)
return false;
if (offerId != null ? !offerId.equals(that.offerId) : that.offerId != null) return false;
return !(depositTxId != null ? !depositTxId.equals(that.depositTxId) : that.depositTxId != null);
}
@Override
public int hashCode() {
int result;
long temp;
result = currency != null ? currency.hashCode() : 0;
result = 31 * result + (direction != null ? direction.hashCode() : 0);
result = 31 * result + (int) (tradePrice ^ (tradePrice >>> 32));
result = 31 * result + (int) (tradeAmount ^ (tradeAmount >>> 32));
result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
result = 31 * result + (int) (offerDate ^ (offerDate >>> 32));
result = 31 * result + (useMarketBasedPrice ? 1 : 0);
temp = Double.doubleToLongBits(marketPriceMargin);
result = 31 * result + (int) (temp ^ (temp >>> 32));
result = 31 * result + (int) (offerAmount ^ (offerAmount >>> 32));
result = 31 * result + (int) (offerMinAmount ^ (offerMinAmount >>> 32));
result = 31 * result + (offerId != null ? offerId.hashCode() : 0);
result = 31 * result + (depositTxId != null ? depositTxId.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "TradeStatistics{" +
"currency='" + currency + '\'' +
", direction=" + direction +
", tradePrice=" + tradePrice +
", tradeAmount=" + tradeAmount +
", tradeDate=" + tradeDate +
", paymentMethod='" + paymentMethod + '\'' +
", offerDate=" + offerDate +
", useMarketBasedPrice=" + useMarketBasedPrice +
", marketPriceMargin=" + marketPriceMargin +
", offerAmount=" + offerAmount +
", offerMinAmount=" + offerMinAmount +
", offerId='" + offerId + '\'' +
", depositTxId='" + depositTxId + '\'' +
", pubKeyRing=" + pubKeyRing +
'}';
}
}

View file

@ -38,7 +38,7 @@ public class TradeStatisticsManager {
HashSet<TradeStatistics> persisted = storage.initAndGetPersistedWithFileName("TradeStatistics");
if (persisted != null)
observableTradeStatisticsSet = FXCollections.observableSet(persisted);
persisted.stream().forEach(e -> add(e));
p2PService.addHashSetChangedListener(new HashMapChangedListener() {
@Override
@ -57,23 +57,28 @@ public class TradeStatisticsManager {
}
public void add(TradeStatistics tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
observableTradeStatisticsSet.add(tradeStatistics);
tradeStatisticsSet.add(tradeStatistics);
storage.queueUpForSave(tradeStatisticsSet, 2000);
if (!tradeStatisticsSet.contains(tradeStatistics)) {
boolean itemAlreadyAdded = tradeStatisticsSet.stream().filter(e -> (e.offerId.equals(tradeStatistics.offerId))).findAny().isPresent();
if (!itemAlreadyAdded) {
tradeStatisticsSet.add(tradeStatistics);
observableTradeStatisticsSet.add(tradeStatistics);
storage.queueUpForSave(tradeStatisticsSet, 2000);
if (dumpStatistics) {
// We store the statistics as json so it is easy for further processing (e.g. for web based services)
// TODO This is just a quick solution for storing to one file.
// 1 statistic entry has 500 bytes as json.
// Need a more scalable solution later when we get more volume.
// The flag will only be activated by dedicated nodes, so it should not be too critical for the moment, but needs to
// get improved. Maybe a LevelDB like DB...? Could be impl. in a headless version only.
List<TradeStatistics> list = tradeStatisticsSet.stream().collect(Collectors.toList());
list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1)));
TradeStatistics[] array = new TradeStatistics[tradeStatisticsSet.size()];
list.toArray(array);
jsonStorage.queueUpForSave(Utilities.objectToJson(array), 5_000);
if (dumpStatistics) {
// We store the statistics as json so it is easy for further processing (e.g. for web based services)
// TODO This is just a quick solution for storing to one file.
// 1 statistic entry has 500 bytes as json.
// Need a more scalable solution later when we get more volume.
// The flag will only be activated by dedicated nodes, so it should not be too critical for the moment, but needs to
// get improved. Maybe a LevelDB like DB...? Could be impl. in a headless version only.
List<TradeStatistics> list = tradeStatisticsSet.stream().collect(Collectors.toList());
list.sort((o1, o2) -> (o1.tradeDate < o2.tradeDate ? 1 : (o1.tradeDate == o2.tradeDate ? 0 : -1)));
TradeStatistics[] array = new TradeStatistics[tradeStatisticsSet.size()];
list.toArray(array);
jsonStorage.queueUpForSave(Utilities.objectToJson(array), 5_000);
}
} else {
log.error("We have already an item with the same offer ID. That might happen if both the offerer and the taker published the tradeStatistics");
}
}
}

View file

@ -31,7 +31,6 @@ import io.bitsquare.trade.protocol.trade.messages.TradeMessage;
import io.bitsquare.trade.protocol.trade.tasks.buyer.*;
import io.bitsquare.trade.protocol.trade.tasks.offerer.*;
import io.bitsquare.trade.protocol.trade.tasks.shared.BroadcastAfterLockTime;
import io.bitsquare.trade.protocol.trade.tasks.shared.PublishTradeStatistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View file

@ -122,7 +122,8 @@ public class BuyerAsTakerProtocol extends TradeProtocol implements BuyerProtocol
VerifyOffererAccount.class,
VerifyAndSignContract.class,
SignAndPublishDepositTxAsBuyer.class,
SendDepositTxPublishedMessage.class
SendDepositTxPublishedMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
}

View file

@ -29,7 +29,6 @@ import io.bitsquare.trade.protocol.trade.messages.*;
import io.bitsquare.trade.protocol.trade.tasks.offerer.*;
import io.bitsquare.trade.protocol.trade.tasks.seller.*;
import io.bitsquare.trade.protocol.trade.tasks.shared.BroadcastAfterLockTime;
import io.bitsquare.trade.protocol.trade.tasks.shared.PublishTradeStatistics;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View file

@ -132,7 +132,8 @@ public class SellerAsTakerProtocol extends TradeProtocol implements SellerProtoc
VerifyOffererAccount.class,
VerifyAndSignContract.class,
SignAndPublishDepositTxAsSeller.class,
SendDepositTxPublishedMessage.class
SendDepositTxPublishedMessage.class,
PublishTradeStatistics.class
);
taskRunner.run();
}

View file

@ -15,7 +15,7 @@
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.trade.protocol.trade.tasks.shared;
package io.bitsquare.trade.protocol.trade.tasks.offerer;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
@ -35,6 +35,7 @@ public class PublishTradeStatistics extends TradeTask {
protected void run() {
try {
runInterceptHook();
// Offerer publishes directly
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),

View file

@ -0,0 +1,78 @@
/*
* 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.taker;
import io.bitsquare.common.taskrunner.TaskRunner;
import io.bitsquare.trade.Trade;
import io.bitsquare.trade.TradeStatistics;
import io.bitsquare.trade.protocol.trade.tasks.TradeTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
public class PublishTradeStatistics extends TradeTask {
private static final Logger log = LoggerFactory.getLogger(PublishTradeStatistics.class);
public PublishTradeStatistics(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
// taker only publishes if the offerer uses an old version
processModel.getP2PService().getNetworkNode().getConfirmedConnections()
.stream()
.filter(c -> c.getPeersNodeAddressOptional().isPresent() && c.getPeersNodeAddressOptional().get().equals(trade.getTradingPeerNodeAddress()))
.findAny()
.ifPresent(c -> {
TradeStatistics tradeStatistics = new TradeStatistics(trade.getOffer(),
trade.getTradePrice(),
trade.getTradeAmount(),
trade.getDate(),
(trade.getDepositTx() != null ? trade.getDepositTx().getHashAsString() : ""),
processModel.getPubKeyRing());
final List<Integer> requiredCapabilities = tradeStatistics.getRequiredCapabilities();
final List<Integer> supportedCapabilities = c.getSupportedCapabilities();
boolean matches = false;
if (supportedCapabilities != null) {
for (int messageCapability : requiredCapabilities) {
for (int connectionCapability : supportedCapabilities) {
if (messageCapability == connectionCapability) {
matches = true;
break;
}
}
}
}
if (!matches) {
log.error("We publish tradeStatistics because the offerer does use an old version.");
processModel.getP2PService().addData(tradeStatistics, true);
} else {
log.error("We do not publish tradeStatistics because the offerer support the capabilities.");
}
});
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -111,7 +111,8 @@ public final class Preferences implements Persistable {
private boolean autoSelectArbitrators = true;
private final Map<String, Boolean> dontShowAgainMap;
private boolean tacAccepted;
private boolean useTorForBitcoinJ = true;
//TODO we set it to false for now as it is not ready yet
private boolean useTorForBitcoinJ = false;
private boolean showOwnOffersInOfferBook = true;
private Locale preferredLocale;
private TradeCurrency preferredTradeCurrency;

View file

@ -26,6 +26,6 @@
xmlns:fx="http://javafx.com/fxml">
<Tab fx:id="chartsTab" text="Offer book" closable="false"/>
<Tab fx:id="tradesTab" text="Trades" closable="false"/>
<Tab fx:id="statisticsTab" text="Spreads" closable="false"/>
<Tab fx:id="tradesTab" text="Trades" closable="false"/>
</TabPane>

View file

@ -28,7 +28,6 @@ public class CandleData {
public final long accumulatedVolume;
public final boolean isBullish;
// public CandleStickExtraValues(double close, double high, double low, double average, double volume) {
public CandleData(long tick, long open, long close, long high, long low, long average, long accumulatedAmount, long accumulatedVolume, boolean isBullish) {
this.tick = tick;
this.open = open;

View file

@ -190,23 +190,29 @@ public class CandleStickChart extends XYChart<Number, Number> {
for (int j = 0; j < series.getData().size(); j++) {
XYChart.Data item = series.getData().get(j);
Node candle = createCandle(seriesIndex, item, j);
getPlotChildren().add(candle);
if (shouldAnimate()) {
candle.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
ft.setToValue(1);
ft.play();
if (!getPlotChildren().contains(candle)) {
getPlotChildren().add(candle);
if (shouldAnimate()) {
candle.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
ft.setToValue(1);
ft.play();
}
}
}
Path seriesPath = new Path();
seriesPath.getStyleClass().setAll("candlestick-average-line", "series" + seriesIndex);
series.setNode(seriesPath);
getPlotChildren().add(seriesPath);
if (shouldAnimate()) {
seriesPath.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), seriesPath);
ft.setToValue(1);
ft.play();
if (!getPlotChildren().contains(seriesPath)) {
getPlotChildren().add(seriesPath);
if (shouldAnimate()) {
seriesPath.setOpacity(0);
FadeTransition ft = new FadeTransition(Duration.millis(500), seriesPath);
ft.setToValue(1);
ft.play();
}
}
}

View file

@ -208,8 +208,8 @@ public class CreateOfferView extends ActivatableViewAndModel<AnchorPane, CreateO
balanceTextField.setTargetAmount(model.dataModel.totalToPayAsCoin.get());
if (DevFlags.STRESS_TEST_MODE)
UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
// if (DevFlags.STRESS_TEST_MODE)
// UserThread.runAfter(this::onShowPayFundsScreen, 200, TimeUnit.MILLISECONDS);
}
}

View file

@ -268,7 +268,7 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
// called form parent as the view does not get notified when the tab is closed
public void onClose() {
Coin balance = model.dataModel.balance.get();
if (balance != null && balance.isPositive() && !model.takeOfferCompleted.get()) {
if (balance != null && balance.isPositive() && !model.takeOfferCompleted.get() && !DevFlags.DEV_MODE) {
model.dataModel.swapTradeToSavings();
new Popup().information("You had already funded that offer.\n" +
"Your funds have been moved to your local Bitsquare wallet and are available for " +
@ -523,7 +523,6 @@ public class TakeOfferView extends ActivatableViewAndModel<AnchorPane, TakeOffer
showTransactionPublishedScreenSubscription = EasyBind.subscribe(model.showTransactionPublishedScreen, newValue -> {
if (newValue && DevFlags.DEV_MODE) {
close();
navigation.navigateTo(MainView.class, PortfolioView.class, PendingTradesView.class);
} else if (newValue && model.getTrade() != null && model.getTrade().errorMessageProperty().get() == null) {
String key = "takeOfferSuccessInfo";
if (preferences.showAgain(key)) {

View file

@ -128,6 +128,8 @@ public class NetworkSettingsView extends ActivatableViewAndModel<GridPane, Activ
@Override
public void activate() {
// TODO we deactive atm as its not ready now
useTorCheckBox.setDisable(true);
useTorCheckBox.setSelected(preferences.getUseTorForBitcoinJ());
useTorCheckBox.setOnAction(event -> {
boolean selected = useTorCheckBox.isSelected();

View file

@ -35,9 +35,12 @@ public class SeedNodesRepository {
new NodeAddress("b66vnevaljo6xt5a.onion:8000"),*/
// v0.4.2
// ...128
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("uadzuib66jupaept.onion:8000"),
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("hbma455xxbqhcuqh.onion:8000"),
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("wgthuiqn3aoiovbm.onion:8000"),
// ...188
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("hbma455xxbqhcuqh.onion:8000"),
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8000") : new NodeAddress("2zxtnprnx5wqr7a3.onion:8000"),
// testnet
@ -50,9 +53,9 @@ public class SeedNodesRepository {
// 3. Shut down the seed node
// 4. Rename the directory with your local onion address
// 5. Edit here your found onion address (new NodeAddress("YOUR_ONION.onion:8002")
new NodeAddress("rxdkppp3vicnbgqt.onion:8002"),
new NodeAddress("brmbf6mf67d2hlm4.onion:8002"),
new NodeAddress("mfla72c4igh5ta2t.onion:8002")
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8002") : new NodeAddress("rxdkppp3vicnbgqt.onion:8002"),
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8002") : new NodeAddress("brmbf6mf67d2hlm4.onion:8002"),
DevFlags.STRESS_TEST_MODE ? new NodeAddress("hlitt7z4bec4kdh4.onion:8002") : new NodeAddress("mfla72c4igh5ta2t.onion:8002")
);
// Addresses are used if the last digit of their port match the network id:

View file

@ -14,6 +14,7 @@ import io.bitsquare.common.handlers.ResultHandler;
import io.bitsquare.common.util.Utilities;
import io.bitsquare.p2p.P2PService;
import io.bitsquare.p2p.P2PServiceListener;
import io.bitsquare.trade.TradeStatisticsManager;
import io.bitsquare.trade.offer.OpenOfferManager;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.bitcoinj.store.BlockStoreException;
@ -30,6 +31,7 @@ public class SeedNode {
private static Environment env;
private final Injector injector;
private final SeedNodeModule seedNodeModule;
private final TradeStatisticsManager tradeStatisticsManager;
private P2PService p2pService;
@ -116,6 +118,9 @@ public class SeedNode {
}
});
// Wee want to persist trade statistics so we need to instantiate the tradeStatisticsManager
tradeStatisticsManager = injector.getInstance(TradeStatisticsManager.class);
}
public void shutDown() {