seller publishes trade statistics if trade amount transferred

This commit is contained in:
woodser 2024-05-24 14:03:18 -04:00
parent 3e2206cf5e
commit 14d17023a8
3 changed files with 57 additions and 30 deletions

View file

@ -424,6 +424,7 @@ public abstract class Trade implements Tradable, Model {
transient private Subscription tradeStateSubscription;
transient private Subscription tradePhaseSubscription;
transient private Subscription payoutStateSubscription;
transient private Subscription disputeStateSubscription;
transient private TaskLooper pollLooper;
transient private Long pollPeriodMs;
transient private Long pollNormalStartTimeMs;
@ -696,6 +697,9 @@ public abstract class Trade implements Tradable, Model {
// auto complete arbitrator trade
if (isArbitrator() && !isCompleted()) processModel.getTradeManager().onTradeCompleted(this);
// maybe publish trade statistic
maybePublishTradeStatistics();
// reset address entries
processModel.getXmrWalletService().resetAddressEntriesForTrade(getId());
}
@ -709,6 +713,16 @@ public abstract class Trade implements Tradable, Model {
});
});
// handle dispute events
disputeStateSubscription = EasyBind.subscribe(disputeStateProperty, newValue -> {
if (!isInitialized || isShutDownStarted) return;
ThreadUtils.submitToPool(() -> {
if (isDisputeClosed()) {
maybePublishTradeStatistics();
}
});
});
// arbitrator syncs idle wallet when payout unlock expected
if (this instanceof ArbitratorTrade) {
idlePayoutSyncer = new IdlePayoutSyncer();
@ -1422,6 +1436,7 @@ public abstract class Trade implements Tradable, Model {
if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
if (tradePhaseSubscription != null) tradePhaseSubscription.unsubscribe();
if (payoutStateSubscription != null) payoutStateSubscription.unsubscribe();
if (disputeStateSubscription != null) disputeStateSubscription.unsubscribe();
});
}
@ -1998,6 +2013,10 @@ public abstract class Trade implements Tradable, Model {
return isArbitrator() ? getBuyer().getDisputeClosedMessage() != null || getSeller().getDisputeClosedMessage() != null : getArbitrator().getDisputeClosedMessage() != null;
}
public boolean isDisputeClosed() {
return getDisputeState().isClosed();
}
public boolean isPaymentReceived() {
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
}
@ -2149,6 +2168,39 @@ public abstract class Trade implements Tradable, Model {
return Math.min(MAX_REPROCESS_DELAY_SECONDS, delay);
}
public void maybePublishTradeStatistics() {
if (shouldPublishTradeStatistics()) doPublishTradeStatistics();
}
public boolean shouldPublishTradeStatistics() {
if (!isSeller()) return false;
return tradeAmountTransferred();
}
private boolean tradeAmountTransferred() {
return isPaymentReceived() || (getDisputeResult() != null && getDisputeResult().getWinner() == DisputeResult.Winner.SELLER);
}
private void doPublishTradeStatistics() {
processModel.getP2PService().findPeersCapabilities(getTradePeer().getNodeAddress())
.filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
.ifPresentOrElse(capabilities -> {
String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
boolean isTorNetworkNode = getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
TradeStatistics3 tradeStatistics = TradeStatistics3.from(this, referralId, isTorNetworkNode);
if (tradeStatistics.isValid()) {
log.info("Publishing trade statistics");
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
} else {
log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
}
},
() -> {
log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
"To avoid duplicates we do not publish from our side.");
});
}
///////////////////////////////////////////////////////////////////////////////////////////
// Private
@ -2566,31 +2618,6 @@ public abstract class Trade implements Tradable, Model {
} else {
getXmrWalletService().resetAddressEntriesForOpenOffer(getId());
}
// seller publishes trade statistics
if (this instanceof SellerTrade) {
checkNotNull(getSeller().getDepositTx());
processModel.getP2PService().findPeersCapabilities(getTradePeer().getNodeAddress())
.filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
.ifPresentOrElse(capabilities -> {
// Our peer has updated, so as we are the seller we will publish the trade statistics.
// The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added)
String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
boolean isTorNetworkNode = getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
TradeStatistics3 tradeStatistics = TradeStatistics3.from(this, referralId, isTorNetworkNode);
if (tradeStatistics.isValid()) {
log.info("Publishing trade statistics");
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
} else {
log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
}
},
() -> {
log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
"To avoid duplicates we do not publish from our side.");
});
}
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -103,6 +103,9 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
for (Dispute dispute : trade.getDisputes()) dispute.setIsClosed();
}
// advance state, arbitrator auto completes when payout published
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
// publish signed witness
SignedWitness signedWitness = message.getBuyerSignedWitness();
if (signedWitness != null && trade instanceof BuyerTrade) {
@ -113,7 +116,6 @@ public class ProcessPaymentReceivedMessage extends TradeTask {
}
// complete
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG); // arbitrator auto completes when payout published
trade.requestPersistence();
complete();
} catch (Throwable t) {

View file

@ -26,7 +26,6 @@ import haveno.core.locale.CurrencyTuple;
import haveno.core.locale.CurrencyUtil;
import haveno.core.locale.Res;
import haveno.core.provider.price.PriceFeedService;
import haveno.core.trade.SellerTrade;
import haveno.core.trade.Trade;
import haveno.core.util.JsonUtil;
import haveno.network.p2p.P2PService;
@ -200,9 +199,8 @@ public class TradeStatisticsManager {
long ts = System.currentTimeMillis();
Set<P2PDataStorage.ByteArray> hashes = tradeStatistics3StorageService.getMapOfAllData().keySet();
trades.forEach(trade -> {
if (!(trade instanceof SellerTrade)) {
log.debug("Trade: {} is not a seller trade, we only republish if we were seller",
trade.getShortId());
if (!trade.shouldPublishTradeStatistics()) {
log.debug("Trade: {} should not publish trade statistics", trade.getShortId());
return;
}