Accept old trade statistic object

The code didn't handle before the use case of new trade statistic objects
created by two old clients. This change make it independent of the cut off date
and allows us at a later point to update all trade statistics objects with
depositTxId value of null.
This commit is contained in:
Christoph Atteneder 2020-02-03 11:37:56 +01:00
parent cad09aabfd
commit 2ad279f99e
No known key found for this signature in database
GPG Key ID: CD5DC1C529CDFD3B
5 changed files with 160 additions and 25 deletions

View File

@ -53,6 +53,8 @@ import java.util.Optional;
import lombok.Value; import lombok.Value;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
@ -63,7 +65,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
@Value @Value
public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload { public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, PersistableEnvelope, CapabilityRequiringPayload, Comparable<TradeStatistics2> {
public static final String MEDIATOR_ADDRESS = "medAddr"; public static final String MEDIATOR_ADDRESS = "medAddr";
public static final String REFUND_AGENT_ADDRESS = "refAddr"; public static final String REFUND_AGENT_ADDRESS = "refAddr";
@ -282,10 +284,30 @@ public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayl
// Since the trade wasn't executed it's better to filter it out to avoid it having an undue influence on the // Since the trade wasn't executed it's better to filter it out to avoid it having an undue influence on the
// BSQ trade stats. // BSQ trade stats.
boolean excludedFailedTrade = offerId.equals("6E5KOI6O-3a06a037-6f03-4bfa-98c2-59f49f73466a-112"); boolean excludedFailedTrade = offerId.equals("6E5KOI6O-3a06a037-6f03-4bfa-98c2-59f49f73466a-112");
boolean depositTxIdValid = depositTxId == null || (tradeDate < CUT_OFF_DATE_FOR_DEPOSIT_TX_ID.getTime() && !depositTxId.isEmpty()); boolean depositTxIdValid = depositTxId == null || !depositTxId.isEmpty();
return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade && depositTxIdValid; return tradeAmount > 0 && tradePrice > 0 && !excludedFailedTrade && depositTxIdValid;
} }
// TODO: Can be removed as soon as everyone uses v1.2.6+
@Override
public int compareTo(@NotNull TradeStatistics2 o) {
if (direction.equals(o.direction) &&
baseCurrency.equals(o.baseCurrency) &&
counterCurrency.equals(o.counterCurrency) &&
offerPaymentMethod.equals(o.offerPaymentMethod) &&
offerDate == o.offerDate &&
offerUseMarketBasedPrice == o.offerUseMarketBasedPrice &&
offerAmount == o.offerAmount &&
offerMinAmount == o.offerMinAmount &&
offerId.equals(o.offerId) &&
tradePrice == o.tradePrice &&
tradeAmount == o.tradeAmount) {
return 0;
}
return -1;
}
@Override @Override
public String toString() { public String toString() {
return "TradeStatistics2{" + return "TradeStatistics2{" +

View File

@ -30,6 +30,7 @@ import bisq.common.storage.JsonFileManager;
import bisq.common.util.Utilities; import bisq.common.util.Utilities;
import com.google.inject.Inject; import com.google.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
@ -39,6 +40,7 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -93,9 +95,22 @@ public class TradeStatisticsManager {
} }
private void addToSet(TradeStatistics2 tradeStatistics) { private void addToSet(TradeStatistics2 tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) { if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
if (observableTradeStatisticsSet.stream().anyMatch(e -> e.getOfferId().equals(tradeStatistics.getOfferId()))) { Optional<TradeStatistics2> duplicate = observableTradeStatisticsSet.stream().filter(
return; e -> e.getOfferId().equals(tradeStatistics.getOfferId())).findAny();
if (duplicate.isPresent()) {
// TODO: Can be removed as soon as everyone uses v1.2.6+
// Removes an existing object with a trade id if the new one matches the existing except
// for the deposit tx id
if (tradeStatistics.getDepositTxId() == null &&
tradeStatistics.isValid() &&
duplicate.get().compareTo(tradeStatistics) == 0) {
observableTradeStatisticsSet.remove(duplicate.get());
} else {
return;
}
} }
if (!tradeStatistics.isValid()) { if (!tradeStatistics.isValid()) {

View File

@ -36,6 +36,7 @@ public class TradeStatistics2Maker {
public static final Property<TradeStatistics2, Date> date = new Property<>(); public static final Property<TradeStatistics2, Date> date = new Property<>();
public static final Property<TradeStatistics2, String> depositTxId = new Property<>(); public static final Property<TradeStatistics2, String> depositTxId = new Property<>();
public static final Property<TradeStatistics2, Coin> tradeAmount = new Property<>();
public static final Instantiator<TradeStatistics2> TradeStatistic2 = lookup -> { public static final Instantiator<TradeStatistics2> TradeStatistic2 = lookup -> {
Calendar calendar = Calendar.getInstance(); Calendar calendar = Calendar.getInstance();
@ -81,7 +82,7 @@ public class TradeStatistics2Maker {
null, null,
0), 0),
Price.valueOf("BTC", 100000L), Price.valueOf("BTC", 100000L),
Coin.SATOSHI, lookup.valueOf(tradeAmount, Coin.SATOSHI),
lookup.valueOf(date, new Date(calendar.getTimeInMillis())), lookup.valueOf(date, new Date(calendar.getTimeInMillis())),
lookup.valueOf(depositTxId, "123456"), lookup.valueOf(depositTxId, "123456"),
Collections.emptyMap()); Collections.emptyMap());

View File

@ -17,24 +17,19 @@
package bisq.core.trade.statistics; package bisq.core.trade.statistics;
import org.apache.commons.lang3.time.DateUtils;
import org.junit.Test; import org.junit.Test;
import static bisq.core.trade.statistics.TradeStatistics2Maker.date;
import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade; import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade;
import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId; import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId;
import static com.natpryce.makeiteasy.MakeItEasy.make; import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.natpryce.makeiteasy.MakeItEasy.withNull; import static com.natpryce.makeiteasy.MakeItEasy.withNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class TradeStatistics2Test { public class TradeStatistics2Test {
@Test @Test
public void isValid_WithDepositTxIdBeforeCutOffDate() { public void isValid_WithDepositTxId() {
TradeStatistics2 tradeStatistic = make(dayZeroTrade); TradeStatistics2 tradeStatistic = make(dayZeroTrade);
@ -42,20 +37,8 @@ public class TradeStatistics2Test {
} }
@Test @Test
public void isValid_Not_WithDepositTxIdAfterCutOffDate() { public void isValid_WithEmptyDepositTxId() {
TradeStatistics2 tradeStatistic = make(dayZeroTrade.but( TradeStatistics2 tradeStatistic = make(dayZeroTrade.but(withNull(depositTxId)));
with(date, DateUtils.addDays(TradeStatistics2.CUT_OFF_DATE_FOR_DEPOSIT_TX_ID, 1))
));
assertFalse(tradeStatistic.isValid());
}
@Test
public void isValid_WithEmptyDepositTxIdAfterCutOffDate() {
TradeStatistics2 tradeStatistic = make(dayZeroTrade.but(
with(date, DateUtils.addDays(TradeStatistics2.CUT_OFF_DATE_FOR_DEPOSIT_TX_ID, 1)),
withNull(depositTxId)
));
assertTrue(tradeStatistic.isValid()); assertTrue(tradeStatistic.isValid());
} }

View File

@ -0,0 +1,114 @@
/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
*/
package bisq.core.trade.statistics;
import bisq.core.provider.price.PriceFeedService;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreListener;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import org.bitcoinj.core.Coin;
import java.io.File;
import org.mockito.ArgumentCaptor;
import org.junit.Before;
import org.junit.Test;
import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade;
import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId;
import static bisq.core.trade.statistics.TradeStatistics2Maker.tradeAmount;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.with;
import static com.natpryce.makeiteasy.MakeItEasy.withNull;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TradeStatisticsManagerTest {
private TradeStatisticsManager manager;
private TradeStatistics2 tradeWithNullDepositTxId;
private ArgumentCaptor<AppendOnlyDataStoreListener> listenerArgumentCaptor;
@Before
public void prepareMocksAndObjects() {
P2PService p2PService = mock(P2PService.class);
P2PDataStorage p2PDataStorage = mock(P2PDataStorage.class);
File storageDir = mock(File.class);
TradeStatistics2StorageService tradeStatistics2StorageService = mock(TradeStatistics2StorageService.class);
PriceFeedService priceFeedService = mock(PriceFeedService.class);
AppendOnlyDataStoreService appendOnlyDataStoreService = mock(AppendOnlyDataStoreService.class);
when(p2PService.getP2PDataStorage()).thenReturn(p2PDataStorage);
manager = new TradeStatisticsManager(p2PService, priceFeedService,
tradeStatistics2StorageService, appendOnlyDataStoreService, storageDir, false);
tradeWithNullDepositTxId = make(dayZeroTrade.but(withNull(depositTxId)));
manager.onAllServicesInitialized();
listenerArgumentCaptor = ArgumentCaptor.forClass(AppendOnlyDataStoreListener.class);
verify(p2PDataStorage).addAppendOnlyDataStoreListener(listenerArgumentCaptor.capture());
}
@Test
public void addToSet_ObjectWithNullDepositTxId() {
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
}
@Test
public void addToSet_RemoveExistingObjectIfObjectWithNullDepositTxIdIsAdded() {
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade);
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
}
@Test
public void addToSet_NotRemoveExistingObjectIfObjectsNotEqual() {
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade.but(with(tradeAmount, Coin.FIFTY_COINS)));
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
}
@Test
public void addToSet_IgnoreObjectIfObjectWithNullDepositTxIdAlreadyExists() {
TradeStatistics2 tradeWithDepositTxId = make(dayZeroTrade);
listenerArgumentCaptor.getValue().onAdded(tradeWithNullDepositTxId);
listenerArgumentCaptor.getValue().onAdded(tradeWithDepositTxId);
assertTrue(manager.getObservableTradeStatisticsSet().contains(tradeWithNullDepositTxId));
assertFalse(manager.getObservableTradeStatisticsSet().contains(tradeWithDepositTxId));
}
}