Skip to content

Commit e4c14f7

Browse files
committed
randomize trade stats +-10% and within 48 hours (hard fork)
1 parent 5141574 commit e4c14f7

File tree

11 files changed

+91
-46
lines changed

11 files changed

+91
-46
lines changed

common/src/main/java/haveno/common/app/Version.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,12 +107,11 @@ private static int getSubVersion(String version, int index) {
107107

108108
// The version no. of the current protocol. The offer holds that version.
109109
// A taker will check the version of the offers to see if his version is compatible.
110-
// For the switch to version 2, offers created with the old version will become invalid and have to be canceled.
111-
// For the switch to version 3, offers created with the old version can be migrated to version 3 just by opening
112110
// the Haveno app.
113111
// Version = 0.0.1 -> TRADE_PROTOCOL_VERSION = 1
114112
// Version = 1.0.19 -> TRADE_PROTOCOL_VERSION = 2
115-
public static final int TRADE_PROTOCOL_VERSION = 2;
113+
// Version = 1.2.0 -> TRADE_PROTOCOL_VERSION = 3
114+
public static final int TRADE_PROTOCOL_VERSION = 3;
116115
private static String p2pMessageVersion;
117116

118117
public static String getP2PMessageVersion() {

core/src/main/java/haveno/core/api/CorePriceService.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,9 @@ public CorePriceService(PriceFeedService priceFeedService, OfferBookService offe
7474
public double getMarketPrice(String currencyCode) throws ExecutionException, InterruptedException, TimeoutException, IllegalArgumentException {
7575
var marketPrice = priceFeedService.requestAllPrices().get(CurrencyUtil.getCurrencyCodeBase(currencyCode));
7676
if (marketPrice == null) {
77-
throw new IllegalArgumentException("Currency not found: " + currencyCode); // message sent to client
77+
throw new IllegalArgumentException("Currency not found: " + currencyCode); // TODO: do not use IllegalArgumentException as message sent to client, return undefined?
78+
} else if (!marketPrice.isExternallyProvidedPrice()) {
79+
throw new IllegalArgumentException("Price is not available externally: " + currencyCode); // TODO: return more complex Price type including price double and isExternal boolean
7880
}
7981
return mapPriceFeedServicePrice(marketPrice.getPrice(), marketPrice.getCurrencyCode());
8082
}

core/src/main/java/haveno/core/offer/CreateOfferService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ public Offer createAndGetOffer(String offerId,
151151
// verify price
152152
boolean useMarketBasedPriceValue = fixedPrice == null &&
153153
useMarketBasedPrice &&
154-
isMarketPriceAvailable(currencyCode) &&
154+
isExternalPriceAvailable(currencyCode) &&
155155
!PaymentMethod.isFixedPriceOnly(paymentAccount.getPaymentMethod().getId());
156156
if (fixedPrice == null && !useMarketBasedPriceValue) {
157157
throw new IllegalArgumentException("Must provide fixed price");
@@ -338,7 +338,7 @@ public Offer createClonedOffer(Offer sourceOffer,
338338
// Private
339339
///////////////////////////////////////////////////////////////////////////////////////////
340340

341-
private boolean isMarketPriceAvailable(String currencyCode) {
341+
private boolean isExternalPriceAvailable(String currencyCode) {
342342
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
343343
return marketPrice != null && marketPrice.isExternallyProvidedPrice();
344344
}

core/src/main/java/haveno/core/trade/HavenoUtils.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
4040
import haveno.core.trade.messages.PaymentReceivedMessage;
4141
import haveno.core.trade.messages.PaymentSentMessage;
42+
import haveno.core.trade.statistics.TradeStatisticsManager;
4243
import haveno.core.user.Preferences;
4344
import haveno.core.util.JsonUtil;
4445
import haveno.core.xmr.wallet.XmrWalletService;
@@ -132,6 +133,7 @@ public static Object getWalletFunctionLock() {
132133
public static XmrConnectionService xmrConnectionService;
133134
public static OpenOfferManager openOfferManager;
134135
public static CoreNotificationService notificationService;
136+
public static TradeStatisticsManager tradeStatisticsManager;
135137
public static Preferences preferences;
136138

137139
public static boolean isSeedNode() {

core/src/main/java/haveno/core/trade/Trade.java

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@
6565
import haveno.core.trade.protocol.TradeListener;
6666
import haveno.core.trade.protocol.TradePeer;
6767
import haveno.core.trade.protocol.TradeProtocol;
68-
import haveno.core.trade.statistics.TradeStatistics3;
6968
import haveno.core.util.VolumeUtil;
7069
import haveno.core.xmr.model.XmrAddressEntry;
7170
import haveno.core.xmr.wallet.XmrWalletBase;
@@ -124,6 +123,7 @@
124123
import java.util.List;
125124
import java.util.Optional;
126125
import java.util.concurrent.ThreadLocalRandom;
126+
import java.util.concurrent.TimeUnit;
127127
import java.util.stream.Collectors;
128128

129129
import static com.google.common.base.Preconditions.checkNotNull;
@@ -2427,12 +2427,27 @@ public long getReprocessDelayInSeconds(int reprocessCount) {
24272427
}
24282428

24292429
public void maybePublishTradeStatistics() {
2430-
if (shouldPublishTradeStatistics()) doPublishTradeStatistics();
2430+
if (shouldPublishTradeStatistics()) {
2431+
2432+
// publish after random delay within 24 hours
2433+
UserThread.runAfterRandomDelay(() -> {
2434+
if (!isShutDownStarted) doPublishTradeStatistics();
2435+
}, 0, 24, TimeUnit.HOURS);
2436+
}
24312437
}
24322438

24332439
public boolean shouldPublishTradeStatistics() {
2434-
if (!isSeller()) return false;
2435-
return tradeAmountTransferred();
2440+
2441+
// do not publish if funds not transferred
2442+
if (!tradeAmountTransferred()) return false;
2443+
2444+
// only seller or arbitrator publish trade stats
2445+
if (!isSeller() && !isArbitrator()) return false;
2446+
2447+
// prior to v3 protocol, only seller publishes trade stats
2448+
if (getOffer().getOfferPayload().getProtocolVersion() < 3 && !isSeller()) return false;
2449+
2450+
return true;
24362451
}
24372452

24382453

@@ -2447,13 +2462,7 @@ private boolean tradeAmountTransferred() {
24472462
private void doPublishTradeStatistics() {
24482463
String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
24492464
boolean isTorNetworkNode = getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
2450-
TradeStatistics3 tradeStatistics = TradeStatistics3.from(this, referralId, isTorNetworkNode, true);
2451-
if (tradeStatistics.isValid()) {
2452-
log.info("Publishing trade statistics for {} {}", getClass().getSimpleName(), getId());
2453-
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
2454-
} else {
2455-
log.warn("Trade statistics are invalid for {} {}. We do not publish: {}", getClass().getSimpleName(), getId(), tradeStatistics);
2456-
}
2465+
HavenoUtils.tradeStatisticsManager.maybePublishTradeStatistics(this, referralId, isTorNetworkNode);
24572466
}
24582467

24592468
// lazy initialization

core/src/main/java/haveno/core/trade/TradeManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,7 +523,7 @@ private void initPersistedTrades() {
523523
nonFailedTrades.addAll(tradableList.getList());
524524
String referralId = referralIdService.getOptionalReferralId().orElse(null);
525525
boolean isTorNetworkNode = p2PService.getNetworkNode() instanceof TorNetworkNode;
526-
tradeStatisticsManager.maybeRepublishTradeStatistics(nonFailedTrades, referralId, isTorNetworkNode);
526+
tradeStatisticsManager.maybePublishTradeStatistics(nonFailedTrades, referralId, isTorNetworkNode);
527527
}).start();
528528

529529
// allow execution to start

core/src/main/java/haveno/core/trade/protocol/tasks/ArbitratorSendInitTradeOrMultisigRequests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ protected void run() {
7171
UUID.randomUUID().toString(),
7272
Version.getP2PMessageVersion(),
7373
request.getAccountAgeWitnessSignatureOfOfferId(),
74-
new Date().getTime(),
74+
request.getCurrentDate(),
7575
trade.getMaker().getNodeAddress(),
7676
trade.getTaker().getNodeAddress(),
7777
trade.getArbitrator().getNodeAddress(),

core/src/main/java/haveno/core/trade/protocol/tasks/MakerSendInitTradeRequestToArbitrator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectM
131131
takerRequest.getUid(),
132132
Version.getP2PMessageVersion(),
133133
null,
134-
takerRequest.getCurrentDate(),
134+
trade.getTakeOfferDate().getTime(), // maker's date is used as shared timestamp
135135
trade.getMaker().getNodeAddress(),
136136
trade.getTaker().getNodeAddress(),
137137
trade.getArbitrator().getNodeAddress(),

core/src/main/java/haveno/core/trade/protocol/tasks/ProcessInitTradeRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ else if (trade instanceof ArbitratorTrade) {
9898
sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
9999
if (sender == trade.getMaker()) {
100100
trade.getTaker().setPubKeyRing(request.getTakerPubKeyRing());
101+
trade.setTakeOfferDate(request.getCurrentDate());
101102

102103
// check trade price
103104
try {
@@ -116,6 +117,7 @@ else if (sender == trade.getTaker()) {
116117
if (!trade.getTaker().getPubKeyRing().equals(request.getTakerPubKeyRing())) throw new RuntimeException("Taker's pub key ring does not match request's pub key ring");
117118
if (request.getTradeAmount() != trade.getAmount().longValueExact()) throw new RuntimeException("Trade amount does not match request's trade amount");
118119
if (request.getTradePrice() != trade.getPrice().getValue()) throw new RuntimeException("Trade price does not match request's trade price");
120+
if (request.getCurrentDate() != trade.getTakeOfferDate().getTime()) throw new RuntimeException("Trade's take offer date does not match request's current date");
119121
}
120122

121123
// handle invalid sender
@@ -134,6 +136,7 @@ else if (trade instanceof TakerTrade) {
134136
trade.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
135137
sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
136138
if (sender != trade.getArbitrator()) throw new RuntimeException("InitTradeRequest to taker is expected from arbitrator");
139+
trade.setTakeOfferDate(request.getCurrentDate());
137140
}
138141

139142
// handle invalid trade type

core/src/main/java/haveno/core/trade/statistics/TradeStatistics3.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,24 @@ public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayl
6969

7070
@JsonExclude
7171
private transient static final ZoneId ZONE_ID = ZoneId.systemDefault();
72-
private static final double FUZZ_AMOUNT_PCT = 0.05;
73-
private static final int FUZZ_DATE_HOURS = 24;
7472

75-
public static TradeStatistics3 from(Trade trade,
76-
@Nullable String referralId,
77-
boolean isTorNetworkNode,
78-
boolean isFuzzed) {
73+
public static TradeStatistics3 from(Trade trade, @Nullable String referralId, boolean isTorNetworkNode) {
74+
return from(trade, referralId, isTorNetworkNode, 0.0, 0);
75+
}
76+
77+
public static TradeStatistics3 fromFuzzedV1(Trade trade, @Nullable String referralId, boolean isTorNetworkNode) {
78+
return from(trade, referralId, isTorNetworkNode, 0.05, 24);
79+
}
80+
81+
public static TradeStatistics3 fromFuzzedV2(Trade trade, @Nullable String referralId, boolean isTorNetworkNode) {
82+
return from(trade, referralId, isTorNetworkNode, 0.10, 48);
83+
}
84+
85+
private static TradeStatistics3 from(Trade trade,
86+
@Nullable String referralId,
87+
boolean isTorNetworkNode,
88+
double fuzzAmountPct,
89+
int fuzzDateHours) {
7990
Map<String, String> extraDataMap = new HashMap<>();
8091
if (referralId != null) {
8192
extraDataMap.put(OfferPayload.REFERRAL_ID, referralId);
@@ -93,26 +104,30 @@ public static TradeStatistics3 from(Trade trade,
93104
Offer offer = checkNotNull(trade.getOffer());
94105
return new TradeStatistics3(offer.getCurrencyCode(),
95106
trade.getPrice().getValue(),
96-
isFuzzed ? fuzzTradeAmountReproducibly(trade) : trade.getAmount().longValueExact(),
107+
fuzzTradeAmountReproducibly(trade, fuzzAmountPct),
97108
offer.getPaymentMethod().getId(),
98-
isFuzzed ? fuzzTradeDateReproducibly(trade) : trade.getTakeOfferDate().getTime(),
109+
fuzzTradeDateReproducibly(trade, fuzzDateHours),
99110
truncatedArbitratorNodeAddress,
100111
extraDataMap);
101112
}
102113

103-
private static long fuzzTradeAmountReproducibly(Trade trade) { // randomize completed trade info #1099
114+
// randomize completed trade info #1099
115+
private static long fuzzTradeAmountReproducibly(Trade trade, double fuzzAmountPct) {
116+
if (fuzzAmountPct == 0.0) return trade.getAmount().longValueExact();
104117
long originalTimestamp = trade.getTakeOfferDate().getTime();
118+
Random random = new Random(originalTimestamp); // pseudo random generator seeded from take offer datestamp
105119
long exactAmount = trade.getAmount().longValueExact();
106-
Random random = new Random(originalTimestamp); // pseudo random generator seeded from take offer datestamp
107-
long adjustedAmount = (long) random.nextDouble(exactAmount * (1.0 - FUZZ_AMOUNT_PCT), exactAmount * (1 + FUZZ_AMOUNT_PCT));
120+
long adjustedAmount = (long) random.nextDouble(exactAmount * (1.0 - fuzzAmountPct), exactAmount * (1.0 + fuzzAmountPct));
108121
log.debug("trade {} fuzzed trade amount for tradeStatistics is {}", trade.getShortId(), adjustedAmount);
109122
return adjustedAmount;
110123
}
111124

112-
private static long fuzzTradeDateReproducibly(Trade trade) { // randomize completed trade info #1099
125+
// randomize completed trade info #1099
126+
private static long fuzzTradeDateReproducibly(Trade trade, int fuzzDateHours) {
127+
if (fuzzDateHours == 0) return trade.getTakeOfferDate().getTime();
113128
long originalTimestamp = trade.getTakeOfferDate().getTime();
114-
Random random = new Random(originalTimestamp); // pseudo random generator seeded from take offer datestamp
115-
long adjustedTimestamp = random.nextLong(originalTimestamp - TimeUnit.HOURS.toMillis(FUZZ_DATE_HOURS), originalTimestamp);
129+
Random random = new Random(originalTimestamp); // pseudo random generator seeded from take offer datestamp
130+
long adjustedTimestamp = random.nextLong(originalTimestamp - TimeUnit.HOURS.toMillis(fuzzDateHours), originalTimestamp);
116131
log.debug("trade {} fuzzed trade datestamp for tradeStatistics is {}", trade.getShortId(), new Date(adjustedTimestamp));
117132
return adjustedTimestamp;
118133
}

0 commit comments

Comments
 (0)