Skip to content

Commit 1e1da75

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

File tree

11 files changed

+94
-47
lines changed

11 files changed

+94
-47
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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ 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) {
157-
throw new IllegalArgumentException("Must provide fixed price");
157+
throw new IllegalArgumentException("Must provide fixed price!!!");
158158
}
159159

160160
// adjust amount and min amount
@@ -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
@@ -124,6 +124,7 @@
124124
import java.util.List;
125125
import java.util.Optional;
126126
import java.util.concurrent.ThreadLocalRandom;
127+
import java.util.concurrent.TimeUnit;
127128
import java.util.stream.Collectors;
128129

129130
import static com.google.common.base.Preconditions.checkNotNull;
@@ -515,7 +516,6 @@ protected Trade(Offer offer,
515516
this.xmrConnectionService = xmrWalletService.getXmrConnectionService();
516517
this.processModel = processModel;
517518
this.uid = uid;
518-
this.takeOfferDate = new Date().getTime();
519519
this.tradeListeners = new ArrayList<TradeListener>();
520520
this.challenge = challenge;
521521

@@ -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+
// do not publish if buyer
2445+
if (isBuyer()) return false;
2446+
2447+
// only seller publishes trade stats prior to v3 protocol
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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import haveno.network.p2p.SendDirectMessageListener;
3232
import lombok.extern.slf4j.Slf4j;
3333

34+
import java.util.Date;
3435
import java.util.HashSet;
3536
import java.util.Set;
3637

@@ -131,7 +132,7 @@ private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectM
131132
takerRequest.getUid(),
132133
Version.getP2PMessageVersion(),
133134
null,
134-
takerRequest.getCurrentDate(),
135+
new Date().getTime(), // shared timestamp for trade
135136
trade.getMaker().getNodeAddress(),
136137
trade.getTaker().getNodeAddress(),
137138
trade.getArbitrator().getNodeAddress(),

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ protected void run() {
6969
sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
7070
if (sender != trade.getTaker()) throw new RuntimeException("InitTradeRequest to maker is expected from taker");
7171
trade.getTaker().setPubKeyRing(request.getTakerPubKeyRing());
72+
trade.setTakeOfferDate(request.getCurrentDate());
7273

7374
// check protocol version
7475
if (request.getTradeProtocolVersion() != TradeProtocolVersion.MULTISIG_2_3) throw new RuntimeException("Trade protocol version is not supported"); // TODO: check if contained in supported versions
@@ -98,6 +99,7 @@ else if (trade instanceof ArbitratorTrade) {
9899
sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
99100
if (sender == trade.getMaker()) {
100101
trade.getTaker().setPubKeyRing(request.getTakerPubKeyRing());
102+
trade.setTakeOfferDate(request.getCurrentDate());
101103

102104
// check trade price
103105
try {
@@ -116,6 +118,7 @@ else if (sender == trade.getTaker()) {
116118
if (!trade.getTaker().getPubKeyRing().equals(request.getTakerPubKeyRing())) throw new RuntimeException("Taker's pub key ring does not match request's pub key ring");
117119
if (request.getTradeAmount() != trade.getAmount().longValueExact()) throw new RuntimeException("Trade amount does not match request's trade amount");
118120
if (request.getTradePrice() != trade.getPrice().getValue()) throw new RuntimeException("Trade price does not match request's trade price");
121+
if (request.getCurrentDate() != trade.getTakeOfferDate().getTime()) throw new RuntimeException("Trade's take offer date does not match request's current date");
119122
}
120123

121124
// handle invalid sender
@@ -134,6 +137,7 @@ else if (trade instanceof TakerTrade) {
134137
trade.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
135138
sender = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
136139
if (sender != trade.getArbitrator()) throw new RuntimeException("InitTradeRequest to taker is expected from arbitrator");
140+
trade.setTakeOfferDate(request.getCurrentDate());
137141
}
138142

139143
// 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)