Merge pull request #4611 from chimp1984/new-trade-statistics

New trade statistics
This commit is contained in:
Christoph Atteneder 2020-10-09 06:43:56 +02:00 committed by GitHub
commit 3687a03695
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
64 changed files with 1170 additions and 975 deletions

View file

@ -41,5 +41,6 @@ public enum Capability {
MEDIATION, // Supports mediation feature MEDIATION, // Supports mediation feature
REFUND_AGENT, // Supports refund agents REFUND_AGENT, // Supports refund agents
TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data. TRADE_STATISTICS_HASH_UPDATE, // We changed the hash method in 1.2.0 and that requires update to 1.2.2 for handling it correctly, otherwise the seed nodes have to process too much data.
NO_ADDRESS_PRE_FIX // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix. NO_ADDRESS_PRE_FIX, // At 1.4.0 we removed the prefix filter for mailbox messages. If a peer has that capability we do not sent the prefix.
TRADE_STATISTICS_3 // We used a new reduced trade statistics model from v1.4.0 on
} }

View file

@ -128,7 +128,6 @@ public class Version {
'}'); '}');
} }
//TODO move to consensus area
public static final byte COMPENSATION_REQUEST = (byte) 0x01; public static final byte COMPENSATION_REQUEST = (byte) 0x01;
public static final byte REIMBURSEMENT_REQUEST = (byte) 0x01; public static final byte REIMBURSEMENT_REQUEST = (byte) 0x01;
public static final byte PROPOSAL = (byte) 0x01; public static final byte PROPOSAL = (byte) 0x01;

View file

@ -24,8 +24,5 @@ package bisq.common.consensus;
* Better to use the excludeFromJsonDataMap (annotated with @JsonExclude; used in PaymentAccountPayload) to * Better to use the excludeFromJsonDataMap (annotated with @JsonExclude; used in PaymentAccountPayload) to
* add a key/value pair. * add a key/value pair.
*/ */
// TODO PubKeyRing and NodeAddress (network) are using UsedForTradeContractJson that is why it is in common module,
// which is a bit weird... Maybe we need either rename common or split it to util and common where common is common code
// used in network and core?
public interface UsedForTradeContractJson { public interface UsedForTradeContractJson {
} }

View file

@ -48,7 +48,6 @@ import java.util.Arrays;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
// TODO is Hmac needed/make sense?
public class Encryption { public class Encryption {
private static final Logger log = LoggerFactory.getLogger(Encryption.class); private static final Logger log = LoggerFactory.getLogger(Encryption.class);

View file

@ -55,7 +55,6 @@ import org.jetbrains.annotations.NotNull;
import static bisq.common.util.Preconditions.checkDir; import static bisq.common.util.Preconditions.checkDir;
// TODO: use a password protection for key storage
@Singleton @Singleton
public class KeyStorage { public class KeyStorage {
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class); private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);

View file

@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public class JsonFileManager { public class JsonFileManager {
private final ThreadPoolExecutor executor = Utilities.getThreadPoolExecutor("JsonFileManagerExecutor", 5, 50, 60); private final ThreadPoolExecutor executor;
private final File dir; private final File dir;
@ -41,6 +41,8 @@ public class JsonFileManager {
public JsonFileManager(File dir) { public JsonFileManager(File dir) {
this.dir = dir; this.dir = dir;
this.executor = Utilities.getThreadPoolExecutor("JsonFileManagerExecutor", 5, 50, 60);
if (!dir.exists()) if (!dir.exists())
if (!dir.mkdir()) if (!dir.mkdir())
log.warn("make dir failed"); log.warn("make dir failed");

View file

@ -48,7 +48,6 @@ public abstract class NetworkEnvelope implements Envelope {
return getNetworkEnvelopeBuilder().build(); return getNetworkEnvelopeBuilder().build();
} }
// todo remove
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() { public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
return getNetworkEnvelopeBuilder().build(); return getNetworkEnvelopeBuilder().build();
} }

View file

@ -22,7 +22,7 @@ import bisq.core.monetary.Price;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.payment.PaymentAccount; import bisq.core.payment.PaymentAccount;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.Version; import bisq.common.app.Version;
@ -196,7 +196,7 @@ public class CoreApi {
walletsService.removeWalletPassword(password); walletsService.removeWalletPassword(password);
} }
public List<TradeStatistics2> getTradeStatistics() { public List<TradeStatistics3> getTradeStatistics() {
return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); return new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
} }

View file

@ -25,6 +25,7 @@ import bisq.core.offer.OpenOfferManager;
import bisq.core.setup.CorePersistedDataHost; import bisq.core.setup.CorePersistedDataHost;
import bisq.core.setup.CoreSetup; import bisq.core.setup.CoreSetup;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager; import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService; import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
@ -220,6 +221,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
try { try {
injector.getInstance(ArbitratorManager.class).shutDown(); injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeStatisticsManager.class).shutDown();
injector.getInstance(XmrTxProofService.class).shutDown(); injector.getInstance(XmrTxProofService.class).shutDown();
injector.getInstance(DaoSetup.class).shutDown(); injector.getInstance(DaoSetup.class).shutDown();
injector.getInstance(AvoidStandbyModeService.class).shutDown(); injector.getInstance(AvoidStandbyModeService.class).shutDown();

View file

@ -154,10 +154,10 @@ public class AssetService implements DaoSetupService, DaoStateListener {
// TradeAmountDateTuple object holding only the data we need. // TradeAmountDateTuple object holding only the data we need.
Map<String, List<TradeAmountDateTuple>> lookupMap = new HashMap<>(); Map<String, List<TradeAmountDateTuple>> lookupMap = new HashMap<>();
tradeStatisticsManager.getObservableTradeStatisticsSet().stream() tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency())) .filter(e -> CurrencyUtil.isCryptoCurrency(e.getCurrency()))
.forEach(e -> { .forEach(e -> {
lookupMap.putIfAbsent(e.getBaseCurrency(), new ArrayList<>()); lookupMap.putIfAbsent(e.getCurrency(), new ArrayList<>());
lookupMap.get(e.getBaseCurrency()).add(new TradeAmountDateTuple(e.getTradeAmount().getValue(), e.getTradeDate().getTime())); lookupMap.get(e.getCurrency()).add(new TradeAmountDateTuple(e.getAmount(), e.getDate()));
}); });
getStatefulAssets().stream() getStatefulAssets().stream()

View file

@ -19,7 +19,7 @@ package bisq.core.offer.availability;
import bisq.core.support.dispute.agent.DisputeAgent; import bisq.core.support.dispute.agent.DisputeAgent;
import bisq.core.support.dispute.agent.DisputeAgentManager; import bisq.core.support.dispute.agent.DisputeAgentManager;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
@ -42,36 +42,37 @@ import static com.google.common.base.Preconditions.checkArgument;
@Slf4j @Slf4j
public class DisputeAgentSelection { public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100;
public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager, public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) { DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager, return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager, disputeAgentManager,
TradeStatistics2.MEDIATOR_ADDRESS); true);
} }
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager, public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) { DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager, return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager, disputeAgentManager,
TradeStatistics2.REFUND_AGENT_ADDRESS); false);
} }
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager, private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager, DisputeAgentManager<T> disputeAgentManager,
String extraMapKey) { boolean isMediator) {
// We take last 100 entries from trade statistics // We take last 100 entries from trade statistics
List<TradeStatistics2> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet()); List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics2::getTradeDate)); list.sort(Comparator.comparing(TradeStatistics3::getDate));
Collections.reverse(list); Collections.reverse(list);
if (!list.isEmpty()) { if (!list.isEmpty()) {
int max = Math.min(list.size(), 100); int max = Math.min(list.size(), LOOK_BACK_RANGE);
list = list.subList(0, max); list = list.subList(0, max);
} }
// We stored only first 4 chars of disputeAgents onion address // We stored only first 4 chars of disputeAgents onion address
List<String> lastAddressesUsedInTrades = list.stream() List<String> lastAddressesUsedInTrades = list.stream()
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null) .map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
.map(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(extraMapKey))
.filter(Objects::nonNull) .filter(Objects::nonNull)
.collect(Collectors.toList()); .collect(Collectors.toList());

View file

@ -54,6 +54,7 @@ import bisq.core.payment.payload.VenmoAccountPayload;
import bisq.core.payment.payload.WeChatPayAccountPayload; import bisq.core.payment.payload.WeChatPayAccountPayload;
import bisq.core.payment.payload.WesternUnionAccountPayload; import bisq.core.payment.payload.WesternUnionAccountPayload;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.common.proto.ProtoResolver; import bisq.common.proto.ProtoResolver;
import bisq.common.proto.ProtobufferRuntimeException; import bisq.common.proto.ProtobufferRuntimeException;
@ -178,6 +179,8 @@ public class CoreProtoResolver implements ProtoResolver {
return BlindVotePayload.fromProto(proto.getBlindVotePayload()); return BlindVotePayload.fromProto(proto.getBlindVotePayload());
case SIGNED_WITNESS: case SIGNED_WITNESS:
return SignedWitness.fromProto(proto.getSignedWitness()); return SignedWitness.fromProto(proto.getSignedWitness());
case TRADE_STATISTICS3:
return TradeStatistics3.fromProto(proto.getTradeStatistics3());
default: default:
throw new ProtobufferRuntimeException("Unknown proto message case (PB.PersistableNetworkPayload). messageCase=" + proto.getMessageCase()); throw new ProtobufferRuntimeException("Unknown proto message case (PB.PersistableNetworkPayload). messageCase=" + proto.getMessageCase());
} }

View file

@ -59,7 +59,6 @@ import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage; import bisq.core.trade.messages.PeerPublishedDelayedPayoutTxMessage;
import bisq.core.trade.messages.RefreshTradeStateRequest; import bisq.core.trade.messages.RefreshTradeStateRequest;
import bisq.core.trade.messages.TraderSignedWitnessMessage; import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.statistics.TradeStatistics;
import bisq.network.p2p.AckMessage; import bisq.network.p2p.AckMessage;
import bisq.network.p2p.BundleOfEnvelopes; import bisq.network.p2p.BundleOfEnvelopes;
@ -265,9 +264,6 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
return RefundAgent.fromProto(proto.getRefundAgent()); return RefundAgent.fromProto(proto.getRefundAgent());
case FILTER: case FILTER:
return Filter.fromProto(proto.getFilter()); return Filter.fromProto(proto.getFilter());
case TRADE_STATISTICS:
// Still used to convert TradeStatistics data from pre v0.6 versions
return TradeStatistics.fromProto(proto.getTradeStatistics());
case MAILBOX_STORAGE_PAYLOAD: case MAILBOX_STORAGE_PAYLOAD:
return MailboxStoragePayload.fromProto(proto.getMailboxStoragePayload()); return MailboxStoragePayload.fromProto(proto.getMailboxStoragePayload());
case OFFER_PAYLOAD: case OFFER_PAYLOAD:

View file

@ -39,6 +39,7 @@ import bisq.core.support.dispute.mediation.MediationDisputeList;
import bisq.core.support.dispute.refund.RefundDisputeList; import bisq.core.support.dispute.refund.RefundDisputeList;
import bisq.core.trade.TradableList; import bisq.core.trade.TradableList;
import bisq.core.trade.statistics.TradeStatistics2Store; import bisq.core.trade.statistics.TradeStatistics2Store;
import bisq.core.trade.statistics.TradeStatistics3Store;
import bisq.core.user.PreferencesPayload; import bisq.core.user.PreferencesPayload;
import bisq.core.user.UserPayload; import bisq.core.user.UserPayload;
@ -126,6 +127,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
return UnconfirmedBsqChangeOutputList.fromProto(proto.getUnconfirmedBsqChangeOutputList()); return UnconfirmedBsqChangeOutputList.fromProto(proto.getUnconfirmedBsqChangeOutputList());
case SIGNED_WITNESS_STORE: case SIGNED_WITNESS_STORE:
return SignedWitnessStore.fromProto(proto.getSignedWitnessStore()); return SignedWitnessStore.fromProto(proto.getSignedWitnessStore());
case TRADE_STATISTICS3_STORE:
return TradeStatistics3Store.fromProto(proto.getTradeStatistics3Store());
default: default:
throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " + throw new ProtobufferRuntimeException("Unknown proto message case(PB.PersistableEnvelope). " +

View file

@ -52,7 +52,6 @@ import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
// TODO use dao parameters for fee
@Slf4j @Slf4j
public class FeeService { public class FeeService {

View file

@ -23,7 +23,7 @@ import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.provider.PriceNodeHttpClient; import bisq.core.provider.PriceNodeHttpClient;
import bisq.core.provider.ProvidersRepository; import bisq.core.provider.ProvidersRepository;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.network.http.HttpClient; import bisq.network.http.HttpClient;
@ -50,6 +50,7 @@ import javafx.beans.property.StringProperty;
import java.time.Instant; import java.time.Instant;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -288,12 +289,12 @@ public class PriceFeedService {
return new Date(epochInMillisAtLastRequest); return new Date(epochInMillisAtLastRequest);
} }
public void applyLatestBisqMarketPrice(Set<TradeStatistics2> tradeStatisticsSet) { public void applyLatestBisqMarketPrice(Set<TradeStatistics3> tradeStatisticsSet) {
// takes about 10 ms for 5000 items // takes about 10 ms for 5000 items
Map<String, List<TradeStatistics2>> mapByCurrencyCode = new HashMap<>(); Map<String, List<TradeStatistics3>> mapByCurrencyCode = new HashMap<>();
tradeStatisticsSet.forEach(e -> { tradeStatisticsSet.forEach(e -> {
final List<TradeStatistics2> list; List<TradeStatistics3> list;
final String currencyCode = e.getCurrencyCode(); String currencyCode = e.getCurrency();
if (mapByCurrencyCode.containsKey(currencyCode)) { if (mapByCurrencyCode.containsKey(currencyCode)) {
list = mapByCurrencyCode.get(currencyCode); list = mapByCurrencyCode.get(currencyCode);
} else { } else {
@ -306,9 +307,9 @@ public class PriceFeedService {
mapByCurrencyCode.values().stream() mapByCurrencyCode.values().stream()
.filter(list -> !list.isEmpty()) .filter(list -> !list.isEmpty())
.forEach(list -> { .forEach(list -> {
list.sort((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate())); list.sort(Comparator.comparing(TradeStatistics3::getTradeDate));
TradeStatistics2 tradeStatistics = list.get(list.size() - 1); TradeStatistics3 tradeStatistics = list.get(list.size() - 1);
setBisqMarketPrice(tradeStatistics.getCurrencyCode(), tradeStatistics.getTradePrice()); setBisqMarketPrice(tradeStatistics.getCurrency(), tradeStatistics.getTradePrice());
}); });
} }

View file

@ -40,7 +40,8 @@ public class CoreNetworkCapabilities {
Capability.SIGNED_ACCOUNT_AGE_WITNESS, Capability.SIGNED_ACCOUNT_AGE_WITNESS,
Capability.REFUND_AGENT, Capability.REFUND_AGENT,
Capability.TRADE_STATISTICS_HASH_UPDATE, Capability.TRADE_STATISTICS_HASH_UPDATE,
Capability.NO_ADDRESS_PRE_FIX Capability.NO_ADDRESS_PRE_FIX,
Capability.TRADE_STATISTICS_3
); );
if (config.daoActivated) { if (config.daoActivated) {

View file

@ -157,7 +157,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
@Override @Override
protected String getDisputeInfo(Dispute dispute) { protected String getDisputeInfo(Dispute dispute) {
String role = Res.get("shared.arbitrator").toLowerCase(); String role = Res.get("shared.arbitrator").toLowerCase();
String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration"; //TODO needs to be created String link = "https://docs.bisq.network/trading-rules.html#legacy-arbitration";
return Res.get("support.initialInfo", role, role, link); return Res.get("support.initialInfo", role, role, link);
} }

View file

@ -24,8 +24,6 @@ import bisq.core.account.witness.AccountAgeWitnessStorageService;
import bisq.core.trade.closed.ClosedTradableManager; import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager; import bisq.core.trade.failed.FailedTradesManager;
import bisq.core.trade.statistics.ReferralIdService; import bisq.core.trade.statistics.ReferralIdService;
import bisq.core.trade.statistics.TradeStatistics2StorageService;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.app.AppModule; import bisq.common.app.AppModule;
import bisq.common.config.Config; import bisq.common.config.Config;
@ -46,8 +44,6 @@ public class TradeModule extends AppModule {
@Override @Override
protected void configure() { protected void configure() {
bind(TradeManager.class).in(Singleton.class); bind(TradeManager.class).in(Singleton.class);
bind(TradeStatisticsManager.class).in(Singleton.class);
bind(TradeStatistics2StorageService.class).in(Singleton.class);
bind(ClosedTradableManager.class).in(Singleton.class); bind(ClosedTradableManager.class).in(Singleton.class);
bind(FailedTradesManager.class).in(Singleton.class); bind(FailedTradesManager.class).in(Singleton.class);
bind(AccountAgeWitnessService.class).in(Singleton.class); bind(AccountAgeWitnessService.class).in(Singleton.class);

View file

@ -24,7 +24,6 @@ import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.PayoutTxPublishedMessage; import bisq.core.trade.messages.PayoutTxPublishedMessage;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
@ -114,8 +113,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
removeMailboxMessageAfterProcessing(message); removeMailboxMessageAfterProcessing(message);
})) }))
.setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class, .setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class, BuyerVerifiesFinalDelayedPayoutTx.class)
PublishTradeStatistics.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
stopTimeout(); stopTimeout();

View file

@ -23,13 +23,13 @@ import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse; import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.TradeMessage; import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.TradeTask; import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerBroadcastPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx; import bisq.core.trade.protocol.tasks.seller.SellerSignAndFinalizePayoutTx;
@ -39,12 +39,17 @@ import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler; import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler; import bisq.common.handlers.ResultHandler;
import bisq.common.util.Utilities;
import java.util.Date;
import java.util.GregorianCalendar;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Slf4j @Slf4j
public abstract class SellerProtocol extends DisputeProtocol { public abstract class SellerProtocol extends DisputeProtocol {
enum SellerEvent implements FluentProtocol.Event { enum SellerEvent implements FluentProtocol.Event {
STARTUP,
PAYMENT_RECEIVED PAYMENT_RECEIVED
} }
@ -52,6 +57,27 @@ public abstract class SellerProtocol extends DisputeProtocol {
super(trade); super(trade);
} }
@Override
protected void onInitialized() {
super.onInitialized();
// We get called the constructor with any possible state and phase. As we don't want to log an error for such
// cases we use the alternative 'given' method instead of 'expect'.
// We only re-publish for about 2 weeks after 1.4.0 release until most nodes have updated to
// achieve sufficient resilience.
boolean currentDateBeforeCutOffDate = new Date().before(Utilities.getUTCDate(2020, GregorianCalendar.NOVEMBER, 1));
given(anyPhase(Trade.Phase.DEPOSIT_PUBLISHED,
Trade.Phase.DEPOSIT_CONFIRMED,
Trade.Phase.FIAT_SENT,
Trade.Phase.FIAT_RECEIVED,
Trade.Phase.PAYOUT_PUBLISHED)
.with(SellerEvent.STARTUP)
.preCondition(currentDateBeforeCutOffDate))
.setup(tasks(SellerPublishesTradeStatistics.class))
.executeTasks();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Mailbox // Mailbox
@ -79,7 +105,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
SellerFinalizesDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class, SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class, SellerPublishesDepositTx.class,
PublishTradeStatistics.class)) SellerPublishesTradeStatistics.class))
.run(() -> { .run(() -> {
// We stop timeout here and don't start a new one as the // We stop timeout here and don't start a new one as the
// SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own // SellerSendsDepositTxAndDelayedPayoutTxMessage repeats the send the message and has it's own

View file

@ -1,80 +0,0 @@
/*
* 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.protocol.tasks;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.TorNetworkNode;
import bisq.common.taskrunner.TaskRunner;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class PublishTradeStatistics extends TradeTask {
public PublishTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
checkNotNull(trade.getDepositTx());
Map<String, String> extraDataMap = new HashMap<>();
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
}
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
// The first 4 chars are sufficient to identify a mediator.
// For testing with regtest/localhost we use the full address as its localhost and would result in
// same values for multiple mediators.
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
String address = networkNode instanceof TorNetworkNode ?
mediatorNodeAddress.getFullAddress().substring(0, 4) :
mediatorNodeAddress.getFullAddress();
extraDataMap.put(TradeStatistics2.MEDIATOR_ADDRESS, address);
Offer offer = checkNotNull(trade.getOffer());
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
trade.getTradePrice(),
checkNotNull(trade.getTradeAmount()),
trade.getDate(),
trade.getDepositTxId(),
extraDataMap);
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
complete();
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -0,0 +1,105 @@
/*
* 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.protocol.tasks.seller;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.NetworkNode;
import bisq.network.p2p.network.TorNetworkNode;
import bisq.common.app.Capability;
import bisq.common.taskrunner.TaskRunner;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class SellerPublishesTradeStatistics extends TradeTask {
public SellerPublishesTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@Override
protected void run() {
try {
runInterceptHook();
checkNotNull(trade.getDepositTx());
processModel.getP2PService().findPeersCapabilities(trade.getTradingPeerNodeAddress())
.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)
Map<String, String> extraDataMap = new HashMap<>();
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
}
NodeAddress mediatorNodeAddress = checkNotNull(trade.getMediatorNodeAddress());
// The first 4 chars are sufficient to identify a mediator.
// For testing with regtest/localhost we use the full address as its localhost and would result in
// same values for multiple mediators.
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
String truncatedMediatorNodeAddress = networkNode instanceof TorNetworkNode ?
mediatorNodeAddress.getFullAddress().substring(0, 4) :
mediatorNodeAddress.getFullAddress();
NodeAddress refundAgentNodeAddress = checkNotNull(trade.getRefundAgentNodeAddress());
String truncatedRefundAgentNodeAddress = networkNode instanceof TorNetworkNode ?
refundAgentNodeAddress.getFullAddress().substring(0, 4) :
refundAgentNodeAddress.getFullAddress();
Offer offer = checkNotNull(trade.getOffer());
TradeStatistics3 tradeStatistics = new TradeStatistics3(offer.getCurrencyCode(),
trade.getTradePrice().getValue(),
trade.getTradeAmountAsLong(),
offer.getPaymentMethod().getId(),
trade.getTakeOfferDate().getTime(),
truncatedMediatorNodeAddress,
truncatedRefundAgentNodeAddress,
extraDataMap);
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);
}
complete();
},
() -> {
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.");
complete();
});
} catch (Throwable t) {
failed(t);
}
}
}

View file

@ -1,236 +0,0 @@
/*
* 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.monetary.Altcoin;
import bisq.core.monetary.AltcoinExchangeRate;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload;
import bisq.network.p2p.storage.payload.ExpirablePayload;
import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import bisq.common.crypto.Sig;
import bisq.common.proto.persistable.PersistablePayload;
import bisq.common.util.CollectionUtils;
import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.JsonExclude;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import java.security.PublicKey;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import lombok.EqualsAndHashCode;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
/**
* @deprecated Was used in pre v0.6.0 version
*/
@Deprecated
@Slf4j
@EqualsAndHashCode(exclude = {"signaturePubKeyBytes"})
@Value
public final class TradeStatistics implements ProcessOncePersistableNetworkPayload, ProtectedStoragePayload, ExpirablePayload, PersistablePayload {
private final OfferPayload.Direction direction;
private final String baseCurrency;
private final String counterCurrency;
private final String offerPaymentMethod;
private final long offerDate;
private final boolean offerUseMarketBasedPrice;
private final double offerMarketPriceMargin;
private final long offerAmount;
private final long offerMinAmount;
private final String offerId;
private final long tradePrice;
private final long tradeAmount;
private final long tradeDate;
private final String depositTxId;
@JsonExclude
private final byte[] signaturePubKeyBytes;
@JsonExclude
transient private final PublicKey signaturePubKey;
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
// field in a class would break that hash and therefore break the storage mechanism.
@Nullable
private Map<String, String> extraDataMap;
public TradeStatistics(OfferPayload offerPayload,
Price tradePrice,
Coin tradeAmount,
Date tradeDate,
String depositTxId,
byte[] signaturePubKeyBytes) {
this(offerPayload.getDirection(),
offerPayload.getBaseCurrencyCode(),
offerPayload.getCounterCurrencyCode(),
offerPayload.getPaymentMethodId(),
offerPayload.getDate(),
offerPayload.isUseMarketBasedPrice(),
offerPayload.getMarketPriceMargin(),
offerPayload.getAmount(),
offerPayload.getMinAmount(),
offerPayload.getId(),
tradePrice.getValue(),
tradeAmount.value,
tradeDate.getTime(),
depositTxId,
signaturePubKeyBytes,
null);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
TradeStatistics(OfferPayload.Direction direction,
String baseCurrency,
String counterCurrency,
String offerPaymentMethod,
long offerDate,
boolean offerUseMarketBasedPrice,
double offerMarketPriceMargin,
long offerAmount,
long offerMinAmount,
String offerId,
long tradePrice,
long tradeAmount,
long tradeDate,
String depositTxId,
byte[] signaturePubKeyBytes,
@Nullable Map<String, String> extraDataMap) {
this.direction = direction;
this.baseCurrency = baseCurrency;
this.counterCurrency = counterCurrency;
this.offerPaymentMethod = offerPaymentMethod;
this.offerDate = offerDate;
this.offerUseMarketBasedPrice = offerUseMarketBasedPrice;
this.offerMarketPriceMargin = offerMarketPriceMargin;
this.offerAmount = offerAmount;
this.offerMinAmount = offerMinAmount;
this.offerId = offerId;
this.tradePrice = tradePrice;
this.tradeAmount = tradeAmount;
this.tradeDate = tradeDate;
this.depositTxId = depositTxId;
this.signaturePubKeyBytes = signaturePubKeyBytes;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
signaturePubKey = Sig.getPublicKeyFromBytes(signaturePubKeyBytes);
}
@Override
public protobuf.StoragePayload toProtoMessage() {
final protobuf.TradeStatistics.Builder builder = protobuf.TradeStatistics.newBuilder()
.setDirection(OfferPayload.Direction.toProtoMessage(direction))
.setBaseCurrency(baseCurrency)
.setCounterCurrency(counterCurrency)
.setPaymentMethodId(offerPaymentMethod)
.setOfferDate(offerDate)
.setOfferUseMarketBasedPrice(offerUseMarketBasedPrice)
.setOfferMarketPriceMargin(offerMarketPriceMargin)
.setOfferAmount(offerAmount)
.setOfferMinAmount(offerMinAmount)
.setOfferId(offerId)
.setTradePrice(tradePrice)
.setTradeAmount(tradeAmount)
.setTradeDate(tradeDate)
.setDepositTxId(depositTxId)
.setSignaturePubKeyBytes(ByteString.copyFrom(signaturePubKeyBytes));
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
return protobuf.StoragePayload.newBuilder().setTradeStatistics(builder).build();
}
public protobuf.TradeStatistics toProtoTradeStatistics() {
return toProtoMessage().getTradeStatistics();
}
public static TradeStatistics fromProto(protobuf.TradeStatistics proto) {
return new TradeStatistics(
OfferPayload.Direction.fromProto(proto.getDirection()),
proto.getBaseCurrency(),
proto.getCounterCurrency(),
proto.getPaymentMethodId(),
proto.getOfferDate(),
proto.getOfferUseMarketBasedPrice(),
proto.getOfferMarketPriceMargin(),
proto.getOfferAmount(),
proto.getOfferMinAmount(),
proto.getOfferId(),
proto.getTradePrice(),
proto.getTradeAmount(),
proto.getTradeDate(),
proto.getDepositTxId(),
proto.getSignaturePubKeyBytes().toByteArray(),
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap());
}
///////////////////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public long getTTL() {
return TimeUnit.DAYS.toMillis(30);
}
@Override
public PublicKey getOwnerPubKey() {
return signaturePubKey;
}
public Date getTradeDate() {
return new Date(tradeDate);
}
public Price getTradePrice() {
return Price.valueOf(getCurrencyCode(), tradePrice);
}
public String getCurrencyCode() {
return baseCurrency.equals("BTC") ? counterCurrency : baseCurrency;
}
public Coin getTradeAmount() {
return Coin.valueOf(tradeAmount);
}
public Volume getTradeVolume() {
if (getTradePrice().getMonetary() instanceof Altcoin)
return new Volume(new AltcoinExchangeRate((Altcoin) getTradePrice().getMonetary()).coinToAltcoin(getTradeAmount()));
else
return new Volume(new ExchangeRate((Fiat) getTradePrice().getMonetary()).coinToFiat(getTradeAmount()));
}
}

View file

@ -61,7 +61,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/** /**
* Serialized size is about 180-210 byte. Nov 2017 we have 5500 objects * Serialized size is about 180-210 byte. Nov 2017 we have 5500 objects
*/ */
@Deprecated
@Slf4j @Slf4j
@Value @Value
public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload, public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload,

View file

@ -29,6 +29,7 @@ import javax.inject.Named;
import java.io.File; import java.io.File;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -65,11 +66,29 @@ public class TradeStatistics2StorageService extends MapStoreService<TradeStatist
@Override @Override
public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMap() { public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMap() {
return store.getMap(); // As it is used for data request and response and we do not want to send any old trade stat data anymore.
return new HashMap<>();
}
// We overwrite that method to receive old trade stats from the network. As we deactivated getMap to not deliver
// hashes we needed to use the getMapOfAllData method to actually store the data.
// That's a bit of a hack but it's just for transition and can be removed after a few months anyway.
// Alternatively we could create a new interface to handle it differently on the other client classes but that
// seems to be not justified as it is needed only temporarily.
@Override
protected PersistableNetworkPayload putIfAbsent(P2PDataStorage.ByteArray hash, PersistableNetworkPayload payload) {
PersistableNetworkPayload previous = getMapOfAllData().putIfAbsent(hash, payload);
return previous;
}
@Override
protected void readFromResources(String postFix) {
// We do not attempt to read from resources as that file is not provided anymore
readStore();
} }
public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMapOfAllData() { public Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> getMapOfAllData() {
return getMap(); return store.getMap();
} }
@Override @Override

View file

@ -0,0 +1,355 @@
/*
* 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.monetary.Altcoin;
import bisq.core.monetary.AltcoinExchangeRate;
import bisq.core.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferUtil;
import bisq.network.p2p.storage.payload.CapabilityRequiringPayload;
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
import bisq.network.p2p.storage.payload.ProcessOncePersistableNetworkPayload;
import bisq.common.app.Capabilities;
import bisq.common.app.Capability;
import bisq.common.crypto.Hash;
import bisq.common.proto.ProtoUtil;
import bisq.common.util.CollectionUtils;
import bisq.common.util.ExtraDataMapValidator;
import bisq.common.util.JsonExclude;
import bisq.common.util.Utilities;
import com.google.protobuf.ByteString;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.ExchangeRate;
import org.bitcoinj.utils.Fiat;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Charsets;
import java.util.Arrays;
import java.util.Date;
import java.util.Map;
import java.util.Optional;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.Nullable;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* This new trade statistics class uses only the bare minimum of data.
* Data size is about 50 bytes in average
*/
@Slf4j
@Getter
public final class TradeStatistics3 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload,
CapabilityRequiringPayload {
// This enum must not change the order as we use the ordinal for storage to reduce data size.
// The payment method string can be quite long and would consume 15% more space.
// When we get a new payment method we can add it to the enum at the end. Old users would add it as string if not
// recognized.
private enum PaymentMethodMapper {
OK_PAY,
CASH_APP,
VENMO,
AUSTRALIA_PAYID, // seems there is a dev trade
UPHOLD,
MONEY_BEAM,
POPMONEY,
REVOLUT,
PERFECT_MONEY,
SEPA,
SEPA_INSTANT,
FASTER_PAYMENTS,
NATIONAL_BANK,
JAPAN_BANK,
SAME_BANK,
SPECIFIC_BANKS,
SWISH,
ALI_PAY,
WECHAT_PAY,
CLEAR_X_CHANGE,
CHASE_QUICK_PAY,
INTERAC_E_TRANSFER,
US_POSTAL_MONEY_ORDER,
CASH_DEPOSIT,
MONEY_GRAM,
WESTERN_UNION,
HAL_CASH,
F2F,
BLOCK_CHAINS,
PROMPT_PAY,
ADVANCED_CASH,
BLOCK_CHAINS_INSTANT
}
private final String currency;
private final long price;
private final long amount;
private final String paymentMethod;
// As only seller is publishing it is the sellers trade date
private final long date;
// Old converted trade stat objects might not have it set
@Nullable
@JsonExclude
private String mediator;
@Nullable
@JsonExclude
private String refundAgent;
// todo should we add referrerId as well? get added to extra map atm but not used so far
// Hash get set in constructor from json of all the other data fields (with hash = null).
@JsonExclude
private final byte[] hash;
// Should be only used in emergency case if we need to add data but do not want to break backward compatibility
// at the P2P network storage checks. The hash of the object will be used to verify if the data is valid. Any new
// field in a class would break that hash and therefore break the storage mechanism.
@Nullable
@JsonExclude
private final Map<String, String> extraDataMap;
public TradeStatistics3(String currency,
long price,
long amount,
String paymentMethod,
long date,
String mediator,
String refundAgent,
@Nullable Map<String, String> extraDataMap) {
this(currency,
price,
amount,
paymentMethod,
date,
mediator,
refundAgent,
extraDataMap,
null);
}
// Used from conversion method where we use the hash of the TradeStatistics2 objects to avoid duplicate entries
public TradeStatistics3(String currency,
long price,
long amount,
String paymentMethod,
long date,
String mediator,
String refundAgent,
@Nullable byte[] hash) {
this(currency,
price,
amount,
paymentMethod,
date,
mediator,
refundAgent,
null,
hash);
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
@VisibleForTesting
public TradeStatistics3(String currency,
long price,
long amount,
String paymentMethod,
long date,
@Nullable String mediator,
@Nullable String refundAgent,
@Nullable Map<String, String> extraDataMap,
@Nullable byte[] hash) {
this.currency = currency;
this.price = price;
this.amount = amount;
String tempPaymentMethod;
try {
tempPaymentMethod = String.valueOf(PaymentMethodMapper.valueOf(paymentMethod).ordinal());
} catch (Throwable t) {
tempPaymentMethod = paymentMethod;
}
this.paymentMethod = tempPaymentMethod;
this.date = date;
this.mediator = mediator;
this.refundAgent = refundAgent;
this.extraDataMap = ExtraDataMapValidator.getValidatedExtraDataMap(extraDataMap);
this.hash = hash == null ? createHash() : hash;
}
public byte[] createHash() {
// We create hash from all fields excluding hash itself. We use json as simple data serialisation.
// TradeDate is different for both peers so we ignore it for hash. ExtraDataMap is ignored as well as at
// software updates we might have different entries which would cause a different hash.
return Hash.getSha256Ripemd160hash(Utilities.objectToJson(this).getBytes(Charsets.UTF_8));
}
private protobuf.TradeStatistics3.Builder getBuilder() {
protobuf.TradeStatistics3.Builder builder = protobuf.TradeStatistics3.newBuilder()
.setCurrency(currency)
.setPrice(price)
.setAmount(amount)
.setPaymentMethod(paymentMethod)
.setDate(date)
.setHash(ByteString.copyFrom(hash));
Optional.ofNullable(mediator).ifPresent(builder::setMediator);
Optional.ofNullable(refundAgent).ifPresent(builder::setRefundAgent);
Optional.ofNullable(extraDataMap).ifPresent(builder::putAllExtraData);
return builder;
}
public protobuf.TradeStatistics3 toProtoTradeStatistics3() {
return getBuilder().build();
}
@Override
public protobuf.PersistableNetworkPayload toProtoMessage() {
return protobuf.PersistableNetworkPayload.newBuilder().setTradeStatistics3(getBuilder()).build();
}
public static TradeStatistics3 fromProto(protobuf.TradeStatistics3 proto) {
return new TradeStatistics3(
proto.getCurrency(),
proto.getPrice(),
proto.getAmount(),
proto.getPaymentMethod(),
proto.getDate(),
ProtoUtil.stringOrNullFromProto(proto.getMediator()),
ProtoUtil.stringOrNullFromProto(proto.getRefundAgent()),
CollectionUtils.isEmpty(proto.getExtraDataMap()) ? null : proto.getExtraDataMap(),
proto.getHash().toByteArray());
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public byte[] getHash() {
return hash;
}
@Override
public boolean verifyHashSize() {
checkNotNull(hash, "hash must not be null");
return hash.length == 20;
}
@Override
public Capabilities getRequiredCapabilities() {
return new Capabilities(Capability.TRADE_STATISTICS_3);
}
public void pruneOptionalData() {
mediator = null;
refundAgent = null;
}
public String getPaymentMethod() {
try {
return PaymentMethodMapper.values()[Integer.parseInt(paymentMethod)].name();
} catch (Throwable ignore) {
return paymentMethod;
}
}
public Date getTradeDate() {
return new Date(date);
}
public Price getTradePrice() {
return Price.valueOf(currency, price);
}
public Coin getTradeAmount() {
return Coin.valueOf(amount);
}
public Volume getTradeVolume() {
if (getTradePrice().getMonetary() instanceof Altcoin) {
return new Volume(new AltcoinExchangeRate((Altcoin) getTradePrice().getMonetary()).coinToAltcoin(getTradeAmount()));
} else {
Volume volume = new Volume(new ExchangeRate((Fiat) getTradePrice().getMonetary()).coinToFiat(getTradeAmount()));
return OfferUtil.getRoundedFiatVolume(volume);
}
}
public boolean isValid() {
return amount > 0 &&
price > 0 &&
date > 0 &&
paymentMethod != null &&
!paymentMethod.isEmpty() &&
currency != null &&
!currency.isEmpty();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof TradeStatistics3)) return false;
TradeStatistics3 that = (TradeStatistics3) o;
if (price != that.price) return false;
if (amount != that.amount) return false;
if (date != that.date) return false;
if (currency != null ? !currency.equals(that.currency) : that.currency != null) return false;
if (paymentMethod != null ? !paymentMethod.equals(that.paymentMethod) : that.paymentMethod != null)
return false;
return Arrays.equals(hash, that.hash);
}
@Override
public int hashCode() {
int result = currency != null ? currency.hashCode() : 0;
result = 31 * result + (int) (price ^ (price >>> 32));
result = 31 * result + (int) (amount ^ (amount >>> 32));
result = 31 * result + (paymentMethod != null ? paymentMethod.hashCode() : 0);
result = 31 * result + (int) (date ^ (date >>> 32));
result = 31 * result + Arrays.hashCode(hash);
return result;
}
@Override
public String toString() {
return "TradeStatistics3{" +
"\n currency='" + currency + '\'' +
",\n price=" + price +
",\n amount=" + amount +
",\n paymentMethod='" + paymentMethod + '\'' +
",\n date=" + date +
",\n mediator='" + mediator + '\'' +
",\n refundAgent='" + refundAgent + '\'' +
",\n hash=" + Utilities.bytesAsHexString(hash) +
",\n extraDataMap=" + extraDataMap +
"\n}";
}
}

View file

@ -0,0 +1,84 @@
/*
* 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.network.p2p.storage.payload.PersistableNetworkPayload;
import bisq.network.p2p.storage.persistence.HistoricalDataStoreService;
import bisq.common.config.Config;
import bisq.common.persistence.PersistenceManager;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class TradeStatistics3StorageService extends HistoricalDataStoreService<TradeStatistics3Store> {
private static final String FILE_NAME = "TradeStatistics3Store";
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TradeStatistics3StorageService(@Named(Config.STORAGE_DIR) File storageDir,
PersistenceManager<TradeStatistics3Store> persistenceManager) {
super(storageDir, persistenceManager);
}
///////////////////////////////////////////////////////////////////////////////////////////
// API
///////////////////////////////////////////////////////////////////////////////////////////
@Override
public String getFileName() {
return FILE_NAME;
}
@Override
protected void initializePersistenceManager() {
persistenceManager.initialize(store, PersistenceManager.Source.NETWORK);
}
@Override
public boolean canHandle(PersistableNetworkPayload payload) {
return payload instanceof TradeStatistics3;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Protected
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected TradeStatistics3Store createStore() {
return new TradeStatistics3Store();
}
public void persistNow() {
persistenceManager.persistNow(() -> {
});
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.persistence.PersistableNetworkPayloadStore;
import com.google.protobuf.Message;
import java.util.List;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
/**
* We store only the payload in the PB file to save disc space. The hash of the payload can be created anyway and
* is only used as key in the map. So we have a hybrid data structure which is represented as list in the protobuffer
* definition and provide a hashMap for the domain access.
*/
@Slf4j
public class TradeStatistics3Store extends PersistableNetworkPayloadStore<TradeStatistics3> {
TradeStatistics3Store() {
}
///////////////////////////////////////////////////////////////////////////////////////////
// PROTO BUFFER
///////////////////////////////////////////////////////////////////////////////////////////
private TradeStatistics3Store(List<TradeStatistics3> list) {
list.forEach(item -> map.put(new P2PDataStorage.ByteArray(item.getHash()), item));
}
public Message toProtoMessage() {
return protobuf.PersistableEnvelope.newBuilder()
.setTradeStatistics3Store(getBuilder())
.build();
}
private protobuf.TradeStatistics3Store.Builder getBuilder() {
List<protobuf.TradeStatistics3> protoList = map.values().stream()
.map(payload -> (TradeStatistics3) payload)
.map(TradeStatistics3::toProtoTradeStatistics3)
.collect(Collectors.toList());
return protobuf.TradeStatistics3Store.newBuilder().addAllItems(protoList);
}
public static TradeStatistics3Store fromProto(protobuf.TradeStatistics3Store proto) {
List<TradeStatistics3> list = proto.getItemsList().stream()
.map(TradeStatistics3::fromProto).collect(Collectors.toList());
return new TradeStatistics3Store(list);
}
public boolean containsKey(P2PDataStorage.ByteArray hash) {
return map.containsKey(hash);
}
}

View file

@ -0,0 +1,182 @@
/*
* 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.offer.availability.DisputeAgentSelection;
import bisq.network.p2p.BootstrapListener;
import bisq.network.p2p.P2PService;
import bisq.network.p2p.storage.P2PDataStorage;
import bisq.network.p2p.storage.payload.PersistableNetworkPayload;
import bisq.network.p2p.storage.persistence.AppendOnlyDataStoreService;
import bisq.common.UserThread;
import bisq.common.config.Config;
import bisq.common.file.FileUtil;
import bisq.common.util.Utilities;
import com.google.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class TradeStatisticsConverter {
private ExecutorService executor;
@Inject
public TradeStatisticsConverter(P2PService p2PService,
P2PDataStorage p2PDataStorage,
TradeStatistics2StorageService tradeStatistics2StorageService,
TradeStatistics3StorageService tradeStatistics3StorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
@Named(Config.STORAGE_DIR) File storageDir) {
File tradeStatistics2Store = new File(storageDir, "TradeStatistics2Store");
appendOnlyDataStoreService.addService(tradeStatistics2StorageService);
p2PService.addP2PServiceListener(new BootstrapListener() {
@Override
public void onTorNodeReady() {
if (!tradeStatistics2Store.exists()) {
return;
}
executor = Utilities.getSingleThreadExecutor("TradeStatisticsConverter");
executor.submit(() -> {
// We convert early once tor is initialized but still not ready to receive data
Map<P2PDataStorage.ByteArray, PersistableNetworkPayload> tempMap = new HashMap<>();
convertToTradeStatistics3(tradeStatistics2StorageService.getMapOfAllData().values())
.forEach(e -> tempMap.put(new P2PDataStorage.ByteArray(e.getHash()), e));
// We map to user thread to avoid potential threading issues
UserThread.execute(() -> {
tradeStatistics3StorageService.getMapOfLiveData().putAll(tempMap);
tradeStatistics3StorageService.persistNow();
});
try {
log.info("We delete now the old trade statistics file as it was converted to the new format.");
FileUtil.deleteFileIfExists(tradeStatistics2Store);
} catch (IOException e) {
e.printStackTrace();
log.error(e.toString());
}
});
}
@Override
public void onUpdatedDataReceived() {
}
});
// We listen to old TradeStatistics2 objects, convert and store them and rebroadcast.
p2PDataStorage.addAppendOnlyDataStoreListener(payload -> {
if (payload instanceof TradeStatistics2) {
TradeStatistics3 tradeStatistics3 = convertToTradeStatistics3((TradeStatistics2) payload, true);
// We add it to the p2PDataStorage, which handles to get the data stored in the maps and maybe
// re-broadcast as tradeStatistics3 object if not already received.
p2PDataStorage.addPersistableNetworkPayload(tradeStatistics3, null, true);
}
});
}
public void shutDown() {
if (executor != null)
executor.shutdown();
}
private static List<TradeStatistics3> convertToTradeStatistics3(Collection<PersistableNetworkPayload> persistableNetworkPayloads) {
List<TradeStatistics3> list = new ArrayList<>();
long ts = System.currentTimeMillis();
// We might have duplicate entries from both traders as the trade date was different from old clients.
// This should not be the case with converting old persisted data as we did filter those out but it is the case
// when we receive old trade stat objects from the network of 2 not updated traders.
// The hash was ignoring the trade date so we use that to get a unique list
Map<P2PDataStorage.ByteArray, TradeStatistics2> mapWithoutDuplicates = new HashMap<>();
persistableNetworkPayloads.stream()
.filter(e -> e instanceof TradeStatistics2)
.map(e -> (TradeStatistics2) e)
.filter(TradeStatistics2::isValid)
.forEach(e -> mapWithoutDuplicates.putIfAbsent(new P2PDataStorage.ByteArray(e.getHash()), e));
log.info("We convert the existing {} trade statistics objects to the new format.", mapWithoutDuplicates.size());
mapWithoutDuplicates.values().stream()
.map(e -> convertToTradeStatistics3(e, false))
.filter(TradeStatistics3::isValid)
.forEach(list::add);
int size = list.size();
log.info("Conversion to {} new trade statistic objects has been completed after {} ms",
size, System.currentTimeMillis() - ts);
// We prune mediator and refundAgent data from all objects but the last 100 as we only use the
// last 100 entries (DisputeAgentSelection.LOOK_BACK_RANGE).
list.sort(Comparator.comparing(TradeStatistics3::getDate));
if (size > DisputeAgentSelection.LOOK_BACK_RANGE) {
int start = size - DisputeAgentSelection.LOOK_BACK_RANGE;
for (int i = start; i < size; i++) {
TradeStatistics3 tradeStatistics3 = list.get(i);
tradeStatistics3.pruneOptionalData();
}
}
return list;
}
private static TradeStatistics3 convertToTradeStatistics3(TradeStatistics2 tradeStatistics2, boolean fromNetwork) {
Map<String, String> extraDataMap = tradeStatistics2.getExtraDataMap();
String mediator = extraDataMap != null ? extraDataMap.get(TradeStatistics2.MEDIATOR_ADDRESS) : null;
String refundAgent = extraDataMap != null ? extraDataMap.get(TradeStatistics2.REFUND_AGENT_ADDRESS) : null;
long time = tradeStatistics2.getTradeDate().getTime();
byte[] hash = null;
if (fromNetwork) {
// We need to avoid that we duplicate tradeStatistics2 objects in case both traders have not udpated yet.
// Before v1.4.0 both traders published the trade statistics. If one trader has updated he will check
// the capabilities of the peer and if the peer has not updated he will leave publishing to the peer, so we
// do not have the problem of duplicated objects.
// To ensure we add only one object we will use the hash of the tradeStatistics2 object which is the same
// for both traders as it excluded the trade date which is different for both.
hash = tradeStatistics2.getHash();
}
return new TradeStatistics3(tradeStatistics2.getCurrencyCode(),
tradeStatistics2.getTradePrice().getValue(),
tradeStatistics2.getTradeAmount().getValue(),
tradeStatistics2.getOfferPaymentMethod(),
time,
mediator,
refundAgent,
hash);
}
}

View file

@ -21,7 +21,6 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.monetary.Volume; import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload;
import bisq.common.util.MathUtils; import bisq.common.util.MathUtils;
@ -38,65 +37,44 @@ import javax.annotation.concurrent.Immutable;
@ToString @ToString
@Slf4j @Slf4j
public final class TradeStatisticsForJson { public final class TradeStatisticsForJson {
public final String currency; public final String currency;
public final OfferPayload.Direction direction;
public final long tradePrice; public final long tradePrice;
public final long tradeAmount; public final long tradeAmount;
public final long tradeDate; public final long tradeDate;
public final String paymentMethod; public final String paymentMethod;
public final long offerDate;
public final boolean useMarketBasedPrice;
public final double marketPriceMargin;
public final long offerAmount;
public final long offerMinAmount;
public final String offerId;
public final String depositTxId;
// primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once) // primaryMarket fields are based on industry standard where primaryMarket is always in the focus (in the app BTC is always in the focus - will be changed in a larger refactoring once)
public String currencyPair; public String currencyPair;
public OfferPayload.Direction primaryMarketDirection;
public long primaryMarketTradePrice; public long primaryMarketTradePrice;
public long primaryMarketTradeAmount; public long primaryMarketTradeAmount;
public long primaryMarketTradeVolume; public long primaryMarketTradeVolume;
public TradeStatisticsForJson(TradeStatistics2 tradeStatistics) { public TradeStatisticsForJson(TradeStatistics3 tradeStatistics) {
this.direction = OfferPayload.Direction.valueOf(tradeStatistics.getDirection().name()); this.currency = tradeStatistics.getCurrency();
this.currency = tradeStatistics.getCurrencyCode(); this.paymentMethod = tradeStatistics.getPaymentMethod();
this.paymentMethod = tradeStatistics.getOfferPaymentMethod(); this.tradePrice = tradeStatistics.getPrice();
this.offerDate = tradeStatistics.getOfferDate(); this.tradeAmount = tradeStatistics.getAmount();
this.useMarketBasedPrice = tradeStatistics.isOfferUseMarketBasedPrice(); this.tradeDate = tradeStatistics.getDate();
this.marketPriceMargin = tradeStatistics.getOfferMarketPriceMargin();
this.offerAmount = tradeStatistics.getOfferAmount();
this.offerMinAmount = tradeStatistics.getOfferMinAmount();
this.offerId = tradeStatistics.getOfferId();
this.tradePrice = tradeStatistics.getTradePrice().getValue();
this.tradeAmount = tradeStatistics.getTradeAmount().getValue();
this.tradeDate = tradeStatistics.getTradeDate().getTime();
this.depositTxId = tradeStatistics.getDepositTxId();
try { try {
final Price tradePrice = getTradePrice(); Price tradePrice = getTradePrice();
if (CurrencyUtil.isCryptoCurrency(currency)) { if (CurrencyUtil.isCryptoCurrency(currency)) {
primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
currencyPair = currency + "/" + Res.getBaseCurrencyCode(); currencyPair = currency + "/" + Res.getBaseCurrencyCode();
primaryMarketTradePrice = tradePrice.getValue(); primaryMarketTradePrice = tradePrice.getValue();
primaryMarketTradeAmount = getTradeVolume() != null ?
primaryMarketTradeAmount = getTradeVolume() != null ? getTradeVolume().getValue() : 0; getTradeVolume().getValue() :
0;
primaryMarketTradeVolume = getTradeAmount().getValue(); primaryMarketTradeVolume = getTradeAmount().getValue();
} else { } else {
primaryMarketDirection = direction;
currencyPair = Res.getBaseCurrencyCode() + "/" + currency; currencyPair = Res.getBaseCurrencyCode() + "/" + currency;
// we use precision 4 for fiat based price but on the markets api we use precision 8 so we scale up by 10000 // we use precision 4 for fiat based price but on the markets api we use precision 8 so we scale up by 10000
primaryMarketTradePrice = (long) MathUtils.scaleUpByPowerOf10(tradePrice.getValue(), 4); primaryMarketTradePrice = (long) MathUtils.scaleUpByPowerOf10(tradePrice.getValue(), 4);
primaryMarketTradeAmount = getTradeAmount().getValue(); primaryMarketTradeAmount = getTradeAmount().getValue();
// we use precision 4 for fiat but on the markets api we use precision 8 so we scale up by 10000 // we use precision 4 for fiat but on the markets api we use precision 8 so we scale up by 10000
primaryMarketTradeVolume = getTradeVolume() != null ? primaryMarketTradeVolume = getTradeVolume() != null ?
(long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) : 0; (long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) :
0;
} }
} catch (Throwable t) { } catch (Throwable t) {
log.error(t.getMessage()); log.error(t.getMessage());
@ -113,6 +91,10 @@ public final class TradeStatisticsForJson {
} }
public Volume getTradeVolume() { public Volume getTradeVolume() {
try {
return getTradePrice().getVolumeByAmount(getTradeAmount()); return getTradePrice().getVolumeByAmount(getTradeAmount());
} catch (Throwable t) {
return Volume.parse("0", currency);
}
} }
} }

View file

@ -32,6 +32,7 @@ import bisq.common.util.Utilities;
import com.google.inject.Inject; import com.google.inject.Inject;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton;
import javafx.collections.FXCollections; import javafx.collections.FXCollections;
import javafx.collections.ObservableSet; import javafx.collections.ObservableSet;
@ -40,94 +41,85 @@ import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j @Slf4j
public class TradeStatisticsManager { public class TradeStatisticsManager {
private final JsonFileManager jsonFileManager;
private final P2PService p2PService; private final P2PService p2PService;
private final PriceFeedService priceFeedService; private final PriceFeedService priceFeedService;
private final TradeStatistics2StorageService tradeStatistics2StorageService; private final TradeStatistics3StorageService tradeStatistics3StorageService;
private final TradeStatisticsConverter tradeStatisticsConverter;
private final File storageDir;
private final boolean dumpStatistics; private final boolean dumpStatistics;
private final ObservableSet<TradeStatistics2> observableTradeStatisticsSet = FXCollections.observableSet(); private final ObservableSet<TradeStatistics3> observableTradeStatisticsSet = FXCollections.observableSet();
private JsonFileManager jsonFileManager;
@Inject @Inject
public TradeStatisticsManager(P2PService p2PService, public TradeStatisticsManager(P2PService p2PService,
PriceFeedService priceFeedService, PriceFeedService priceFeedService,
TradeStatistics2StorageService tradeStatistics2StorageService, TradeStatistics3StorageService tradeStatistics3StorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService, AppendOnlyDataStoreService appendOnlyDataStoreService,
TradeStatisticsConverter tradeStatisticsConverter,
@Named(Config.STORAGE_DIR) File storageDir, @Named(Config.STORAGE_DIR) File storageDir,
@Named(Config.DUMP_STATISTICS) boolean dumpStatistics) { @Named(Config.DUMP_STATISTICS) boolean dumpStatistics) {
this.p2PService = p2PService; this.p2PService = p2PService;
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
this.tradeStatistics2StorageService = tradeStatistics2StorageService; this.tradeStatistics3StorageService = tradeStatistics3StorageService;
this.tradeStatisticsConverter = tradeStatisticsConverter;
this.storageDir = storageDir;
this.dumpStatistics = dumpStatistics; this.dumpStatistics = dumpStatistics;
jsonFileManager = new JsonFileManager(storageDir);
appendOnlyDataStoreService.addService(tradeStatistics2StorageService);
appendOnlyDataStoreService.addService(tradeStatistics3StorageService);
}
public void shutDown() {
tradeStatisticsConverter.shutDown();
if (jsonFileManager != null) {
jsonFileManager.shutDown();
}
} }
public void onAllServicesInitialized() { public void onAllServicesInitialized() {
p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(payload -> { p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(payload -> {
if (payload instanceof TradeStatistics2) if (payload instanceof TradeStatistics3) {
addToSet((TradeStatistics2) payload); TradeStatistics3 tradeStatistics = (TradeStatistics3) payload;
});
Set<TradeStatistics2> set = tradeStatistics2StorageService.getMapOfAllData().values().stream()
.filter(e -> e instanceof TradeStatistics2)
.map(e -> (TradeStatistics2) e)
.map(WrapperTradeStatistics2::new)
.distinct()
.map(WrapperTradeStatistics2::unwrap)
.filter(TradeStatistics2::isValid)
.collect(Collectors.toSet());
observableTradeStatisticsSet.addAll(set);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump();
}
public ObservableSet<TradeStatistics2> getObservableTradeStatisticsSet() {
return observableTradeStatisticsSet;
}
private void addToSet(TradeStatistics2 tradeStatistics) {
if (!observableTradeStatisticsSet.contains(tradeStatistics)) {
Optional<TradeStatistics2> duplicate = observableTradeStatisticsSet.stream().filter(
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()) {
return; return;
} }
observableTradeStatisticsSet.add(tradeStatistics); observableTradeStatisticsSet.add(tradeStatistics);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet); priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump(); maybeDumpStatistics();
} }
});
Set<TradeStatistics3> set = tradeStatistics3StorageService.getMapOfAllData().values().stream()
.filter(e -> e instanceof TradeStatistics3)
.map(e -> (TradeStatistics3) e)
.filter(TradeStatistics3::isValid)
.collect(Collectors.toSet());
observableTradeStatisticsSet.addAll(set);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
maybeDumpStatistics();
} }
private void dump() { public ObservableSet<TradeStatistics3> getObservableTradeStatisticsSet() {
if (dumpStatistics) { return observableTradeStatisticsSet;
}
private void maybeDumpStatistics() {
if (!dumpStatistics) {
return;
}
if (jsonFileManager == null) {
jsonFileManager = new JsonFileManager(storageDir);
// We only dump once the currencies as they do not change during runtime
ArrayList<CurrencyTuple> fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream() ArrayList<CurrencyTuple> fiatCurrencyList = CurrencyUtil.getAllSortedFiatCurrencies().stream()
.map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8)) .map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
.collect(Collectors.toCollection(ArrayList::new)); .collect(Collectors.toCollection(ArrayList::new));
@ -138,44 +130,14 @@ public class TradeStatisticsManager {
.collect(Collectors.toCollection(ArrayList::new)); .collect(Collectors.toCollection(ArrayList::new));
cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8)); cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8));
jsonFileManager.writeToDisc(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list"); jsonFileManager.writeToDisc(Utilities.objectToJson(cryptoCurrencyList), "crypto_currency_list");
}
// We store the statistics as json so it is easy for further processing (e.g. for web based services) List<TradeStatisticsForJson> list = observableTradeStatisticsSet.stream()
// TODO This is just a quick solution for storing to one file. .map(TradeStatisticsForJson::new)
// 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<TradeStatisticsForJson> list = observableTradeStatisticsSet.stream().map(TradeStatisticsForJson::new)
.sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate))) .sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate)))
.collect(Collectors.toList()); .collect(Collectors.toList());
TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()]; TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()];
list.toArray(array); list.toArray(array);
jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics"); jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics");
} }
}
static class WrapperTradeStatistics2 {
private final TradeStatistics2 tradeStatistics;
public WrapperTradeStatistics2(TradeStatistics2 tradeStatistics) {
this.tradeStatistics = tradeStatistics;
}
public TradeStatistics2 unwrap() {
return this.tradeStatistics;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
var wrapper = (WrapperTradeStatistics2) obj;
return Objects.equals(tradeStatistics.getOfferId(), wrapper.tradeStatistics.getOfferId());
}
@Override
public int hashCode() {
return Objects.hash(tradeStatistics.getOfferId());
}
}
} }

View file

@ -1,91 +0,0 @@
/*
* 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.monetary.Price;
import bisq.core.offer.OfferPayload;
import org.bitcoinj.core.Coin;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import com.natpryce.makeiteasy.Instantiator;
import com.natpryce.makeiteasy.Maker;
import com.natpryce.makeiteasy.Property;
import static com.natpryce.makeiteasy.MakeItEasy.a;
public class TradeStatistics2Maker {
public static final Property<TradeStatistics2, Date> date = 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 -> {
Calendar calendar = Calendar.getInstance();
calendar.set(2016, 3, 19);
return new TradeStatistics2(
new OfferPayload("1234",
0L,
null,
null,
OfferPayload.Direction.BUY,
100000L,
0.0,
false,
100000L,
100000L,
"BTC",
"USD",
null,
null,
"SEPA",
"",
null,
null,
null,
null,
null,
"",
0L,
0L,
0L,
false,
0L,
0L,
0L,
0L,
false,
false,
0L,
0L,
false,
null,
null,
0),
Price.valueOf("BTC", 100000L),
lookup.valueOf(tradeAmount, Coin.SATOSHI),
lookup.valueOf(date, new Date(calendar.getTimeInMillis())),
lookup.valueOf(depositTxId, "123456"),
Collections.emptyMap());
};
public static final Maker<TradeStatistics2> dayZeroTrade = a(TradeStatistic2);
}

View file

@ -1,45 +0,0 @@
/*
* 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 org.junit.Test;
import static bisq.core.trade.statistics.TradeStatistics2Maker.dayZeroTrade;
import static bisq.core.trade.statistics.TradeStatistics2Maker.depositTxId;
import static com.natpryce.makeiteasy.MakeItEasy.make;
import static com.natpryce.makeiteasy.MakeItEasy.withNull;
import static org.junit.Assert.assertTrue;
public class TradeStatistics2Test {
@Test
public void isValid_WithDepositTxId() {
TradeStatistics2 tradeStatistic = make(dayZeroTrade);
assertTrue(tradeStatistic.isValid());
}
@Test
public void isValid_WithEmptyDepositTxId() {
TradeStatistics2 tradeStatistic = make(dayZeroTrade.but(withNull(depositTxId)));
assertTrue(tradeStatistic.isValid());
}
}

View file

@ -1,114 +0,0 @@
/*
* 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));
}
}

View file

@ -1,7 +1,7 @@
package bisq.daemon.grpc; package bisq.daemon.grpc;
import bisq.core.api.CoreApi; import bisq.core.api.CoreApi;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.proto.grpc.GetTradeStatisticsGrpc; import bisq.proto.grpc.GetTradeStatisticsGrpc;
import bisq.proto.grpc.GetTradeStatisticsReply; import bisq.proto.grpc.GetTradeStatisticsReply;
@ -27,7 +27,7 @@ class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStati
StreamObserver<GetTradeStatisticsReply> responseObserver) { StreamObserver<GetTradeStatisticsReply> responseObserver) {
var tradeStatistics = coreApi.getTradeStatistics().stream() var tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2) .map(TradeStatistics3::toProtoTradeStatistics3)
.collect(Collectors.toList()); .collect(Collectors.toList());
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build(); var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();

View file

@ -78,9 +78,6 @@ public class AddressTextField extends AnchorPane {
}); });
textField.focusTraversableProperty().set(focusTraversableProperty().get()); textField.focusTraversableProperty().set(focusTraversableProperty().get());
//TODO app wide focus
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
Label extWalletIcon = new Label(); Label extWalletIcon = new Label();
extWalletIcon.setLayoutY(3); extWalletIcon.setLayoutY(3);
extWalletIcon.getStyleClass().addAll("icon", "highlight"); extWalletIcon.getStyleClass().addAll("icon", "highlight");

View file

@ -74,9 +74,6 @@ public class BsqAddressTextField extends AnchorPane {
}); });
textField.focusTraversableProperty().set(focusTraversableProperty().get()); textField.focusTraversableProperty().set(focusTraversableProperty().get());
//TODO app wide focus
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
Label copyIcon = new Label(); Label copyIcon = new Label();
copyIcon.setLayoutY(3); copyIcon.setLayoutY(3);

View file

@ -79,9 +79,6 @@ public class TextFieldWithCopyIcon extends AnchorPane {
AnchorPane.setRightAnchor(textField, 30.0); AnchorPane.setRightAnchor(textField, 30.0);
AnchorPane.setLeftAnchor(textField, 0.0); AnchorPane.setLeftAnchor(textField, 0.0);
textField.focusTraversableProperty().set(focusTraversableProperty().get()); textField.focusTraversableProperty().set(focusTraversableProperty().get());
//TODO app wide focus
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
getChildren().addAll(textField, copyIcon); getChildren().addAll(textField, copyIcon);
} }

View file

@ -64,8 +64,6 @@ import static bisq.desktop.util.FormBuilder.addMultilineLabel;
import static bisq.desktop.util.FormBuilder.addTitledGroupBg; import static bisq.desktop.util.FormBuilder.addTitledGroupBg;
import static bisq.desktop.util.FormBuilder.addTopLabelTextField; import static bisq.desktop.util.FormBuilder.addTopLabelTextField;
// TODO translation string keys should renamed to be more generic.
// Lets do it for 1.1.7 the translator have time to add new string.
public abstract class AgentRegistrationView<R extends DisputeAgent, T extends AgentRegistrationViewModel<R, ?>> public abstract class AgentRegistrationView<R extends DisputeAgent, T extends AgentRegistrationViewModel<R, ?>>
extends ActivatableViewAndModel<VBox, T> { extends ActivatableViewAndModel<VBox, T> {

View file

@ -30,7 +30,7 @@ import bisq.core.locale.Res;
import bisq.core.monetary.Altcoin; import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
@ -121,7 +121,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private BsqDashboardView(DaoFacade daoFacade, public BsqDashboardView(DaoFacade daoFacade,
TradeStatisticsManager tradeStatisticsManager, TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService, PriceFeedService priceFeedService,
Preferences preferences, Preferences preferences,
@ -173,7 +173,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow, marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
Res.get("dao.factsAndFigures.dashboard.marketCap")).second; Res.get("dao.factsAndFigures.dashboard.marketCap")).second;
availableAmountTextField = FormBuilder.addTopLabelReadOnlyTextField(root, gridRow, 1, availableAmountTextField = addTopLabelReadOnlyTextField(root, gridRow, 1,
Res.get("dao.factsAndFigures.dashboard.availableAmount")).second; Res.get("dao.factsAndFigures.dashboard.availableAmount")).second;
} }
@ -289,17 +289,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private void updateBsqPriceData() { private void updateBsqPriceData() {
seriesBSQPrice.getData().clear(); seriesBSQPrice.getData().clear();
Map<LocalDate, List<TradeStatistics2>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() Map<LocalDate, List<TradeStatistics3>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("BSQ")) .filter(e -> e.getCurrency().equals("BSQ"))
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) .sorted(Comparator.comparing(TradeStatistics3::getDate))
.collect(Collectors.groupingBy(item -> new java.sql.Date(item.getTradeDate().getTime()).toLocalDate() .collect(Collectors.groupingBy(item -> new java.sql.Date(item.getDate()).toLocalDate()
.with(ADJUSTERS.get(DAY)))); .with(ADJUSTERS.get(DAY))));
List<XYChart.Data<Number, Number>> updatedBSQPrice = bsqPriceByDate.keySet().stream() List<XYChart.Data<Number, Number>> updatedBSQPrice = bsqPriceByDate.keySet().stream()
.map(e -> { .map(e -> {
ZonedDateTime zonedDateTime = e.atStartOfDay(ZoneId.systemDefault()); ZonedDateTime zonedDateTime = e.atStartOfDay(ZoneId.systemDefault());
return new XYChart.Data<Number, Number>(zonedDateTime.toInstant().getEpochSecond(), bsqPriceByDate.get(e).stream() return new XYChart.Data<Number, Number>(zonedDateTime.toInstant().getEpochSecond(), bsqPriceByDate.get(e).stream()
.map(TradeStatistics2::getTradePrice) .map(TradeStatistics3::getTradePrice)
.mapToDouble(Price::getValue) .mapToDouble(Price::getValue)
.average() .average()
.orElse(Double.NaN) .orElse(Double.NaN)
@ -370,12 +370,12 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) { private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) {
Date pastXDays = getPastDate(days); Date pastXDays = getPastDate(days);
List<TradeStatistics2> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() List<TradeStatistics3> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("BSQ")) .filter(e -> e.getCurrency().equals("BSQ"))
.filter(e -> e.getTradeDate().after(pastXDays)) .filter(e -> e.getTradeDate().after(pastXDays))
.collect(Collectors.toList()); .collect(Collectors.toList());
List<TradeStatistics2> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() List<TradeStatistics3> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("USD")) .filter(e -> e.getCurrency().equals("USD"))
.filter(e -> e.getTradeDate().after(pastXDays)) .filter(e -> e.getTradeDate().after(pastXDays))
.collect(Collectors.toList()); .collect(Collectors.toList());
long average = isUSDField ? getUSDAverage(bsqTradePastXDays, usdTradePastXDays) : long average = isUSDField ? getUSDAverage(bsqTradePastXDays, usdTradePastXDays) :
@ -391,11 +391,11 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
return average; return average;
} }
private long getBTCAverage(List<TradeStatistics2> bsqList) { private long getBTCAverage(List<TradeStatistics3> bsqList) {
long accumulatedVolume = 0; long accumulatedVolume = 0;
long accumulatedAmount = 0; long accumulatedAmount = 0;
for (TradeStatistics2 item : bsqList) { for (TradeStatistics3 item : bsqList) {
accumulatedVolume += item.getTradeVolume().getValue(); accumulatedVolume += item.getTradeVolume().getValue();
accumulatedAmount += item.getTradeAmount().getValue(); // Amount of BTC traded accumulatedAmount += item.getTradeAmount().getValue(); // Amount of BTC traded
} }
@ -406,17 +406,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
return averagePrice; return averagePrice;
} }
private long getUSDAverage(List<TradeStatistics2> bsqList, List<TradeStatistics2> usdList) { private long getUSDAverage(List<TradeStatistics3> bsqList, List<TradeStatistics3> usdList) {
// Use next USD/BTC print as price to calculate BSQ/USD rate // Use next USD/BTC print as price to calculate BSQ/USD rate
// Store each trade as amount of USD and amount of BSQ traded // Store each trade as amount of USD and amount of BSQ traded
List<Tuple2<Double, Double>> usdBsqList = new ArrayList<>(bsqList.size()); List<Tuple2<Double, Double>> usdBsqList = new ArrayList<>(bsqList.size());
usdList.sort(Comparator.comparing(o -> o.getTradeDate().getTime())); usdList.sort(Comparator.comparing(TradeStatistics3::getDate));
var usdBTCPrice = 10000d; // Default to 10000 USD per BTC if there is no USD feed at all var usdBTCPrice = 10000d; // Default to 10000 USD per BTC if there is no USD feed at all
for (TradeStatistics2 item : bsqList) { for (TradeStatistics3 item : bsqList) {
// Find usdprice for trade item // Find usdprice for trade item
usdBTCPrice = usdList.stream() usdBTCPrice = usdList.stream()
.filter(usd -> usd.getTradeDate().getTime() > item.getTradeDate().getTime()) .filter(usd -> usd.getDate() > item.getDate())
.map(usd -> MathUtils.scaleDownByPowerOf10((double) usd.getTradePrice().getValue(), .map(usd -> MathUtils.scaleDownByPowerOf10((double) usd.getTradePrice().getValue(),
Fiat.SMALLEST_UNIT_EXPONENT)) Fiat.SMALLEST_UNIT_EXPONENT))
.findFirst() .findFirst()

View file

@ -27,7 +27,6 @@ import bisq.core.offer.placeoffer.tasks.AddToOfferBook;
import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx; import bisq.core.offer.placeoffer.tasks.CreateMakerFeeTx;
import bisq.core.offer.placeoffer.tasks.ValidateOffer; import bisq.core.offer.placeoffer.tasks.ValidateOffer;
import bisq.core.trade.protocol.tasks.ApplyFilter; import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.PublishTradeStatistics;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness; import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
@ -56,6 +55,7 @@ import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage; import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse; import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx; import bisq.core.trade.protocol.tasks.seller.SellerPublishesDepositTx;
import bisq.core.trade.protocol.tasks.seller.SellerPublishesTradeStatistics;
import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest; import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage; import bisq.core.trade.protocol.tasks.seller.SellerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
@ -145,7 +145,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerFinalizesDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class, SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class, SellerPublishesDepositTx.class,
PublishTradeStatistics.class, SellerPublishesTradeStatistics.class,
SellerProcessCounterCurrencyTransferStartedMessage.class, SellerProcessCounterCurrencyTransferStartedMessage.class,
ApplyFilter.class, ApplyFilter.class,
@ -179,7 +179,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class, BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class, BuyerVerifiesFinalDelayedPayoutTx.class,
PublishTradeStatistics.class,
ApplyFilter.class, ApplyFilter.class,
MakerVerifyTakerFeePayment.class, MakerVerifyTakerFeePayment.class,
@ -216,7 +215,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class, BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class, BuyerVerifiesFinalDelayedPayoutTx.class,
PublishTradeStatistics.class,
ApplyFilter.class, ApplyFilter.class,
TakerVerifyMakerFeePayment.class, TakerVerifyMakerFeePayment.class,
@ -248,7 +246,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerFinalizesDelayedPayoutTx.class, SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class, SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class, SellerPublishesDepositTx.class,
PublishTradeStatistics.class, SellerPublishesTradeStatistics.class,
SellerProcessCounterCurrencyTransferStartedMessage.class, SellerProcessCounterCurrencyTransferStartedMessage.class,
ApplyFilter.class, ApplyFilter.class,

View file

@ -35,7 +35,7 @@ import bisq.desktop.util.DisplayUtils;
import bisq.core.locale.CurrencyUtil; import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinFormatter;
@ -82,7 +82,10 @@ public class MarketView extends ActivatableView<TabPane, Void> {
@Inject @Inject
public MarketView(CachingViewLoader viewLoader, P2PService p2PService, OfferBook offerBook, @Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter, public MarketView(CachingViewLoader viewLoader,
P2PService p2PService,
OfferBook offerBook,
@Named(FormattingUtils.BTC_FORMATTER_KEY) CoinFormatter formatter,
Navigation navigation) { Navigation navigation) {
this.viewLoader = viewLoader; this.viewLoader = viewLoader;
this.p2PService = p2PService; this.p2PService = p2PService;
@ -179,20 +182,19 @@ public class MarketView extends ActivatableView<TabPane, Void> {
// If both traders had set it the tradeStatistics is only delivered once. // If both traders had set it the tradeStatistics is only delivered once.
// If both traders used a different referral ID then we would get 2 objects. // If both traders used a different referral ID then we would get 2 objects.
List<String> list = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream() List<String> list = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
.filter(e -> e instanceof TradeStatistics2) .filter(e -> e instanceof TradeStatistics3)
.map(e -> (TradeStatistics2) e) .map(e -> (TradeStatistics3) e)
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null) .filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap() != null)
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null) .filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null)
.map(trade -> { .map(tradeStatistics3 -> {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("Trade ID: ").append(trade.getOfferId()).append("\n") sb.append("Date: ").append(DisplayUtils.formatDateTime(tradeStatistics3.getTradeDate())).append("\n")
.append("Date: ").append(DisplayUtils.formatDateTime(trade.getTradeDate())).append("\n") .append("Market: ").append(CurrencyUtil.getCurrencyPair(tradeStatistics3.getCurrency())).append("\n")
.append("Market: ").append(CurrencyUtil.getCurrencyPair(trade.getCurrencyCode())).append("\n") .append("Price: ").append(FormattingUtils.formatPrice(tradeStatistics3.getTradePrice())).append("\n")
.append("Price: ").append(FormattingUtils.formatPrice(trade.getTradePrice())).append("\n") .append("Amount: ").append(formatter.formatCoin(tradeStatistics3.getTradeAmount())).append("\n")
.append("Amount: ").append(formatter.formatCoin(trade.getTradeAmount())).append("\n") .append("Volume: ").append(DisplayUtils.formatVolume(tradeStatistics3.getTradeVolume())).append("\n")
.append("Volume: ").append(DisplayUtils.formatVolume(trade.getTradeVolume())).append("\n") .append("Payment method: ").append(Res.get(tradeStatistics3.getPaymentMethod())).append("\n")
.append("Payment method: ").append(Res.get(trade.getOfferPaymentMethod())).append("\n") .append("ReferralID: ").append(tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
.append("ReferralID: ").append(trade.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
return sb.toString(); return sb.toString();
}) })
.collect(Collectors.toList()); .collect(Collectors.toList());

View file

@ -298,7 +298,7 @@ public class SpreadView extends ActivatableViewAndModel<GridPane, SpreadViewMode
public void updateItem(final SpreadItem item, boolean empty) { public void updateItem(final SpreadItem item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null && !empty) { if (item != null && !empty) {
// TODO maybe show exra colums with item.priceSpread and use real amount diff // TODO maybe show extra columns with item.priceSpread and use real amount diff
// not % based // not % based
if (item.priceSpread != null) if (item.priceSpread != null)
setText(item.percentage); setText(item.percentage);

View file

@ -172,7 +172,7 @@ class SpreadViewModel extends ActivatableViewModel {
else else
spread = bestBuyOfferPrice.subtract(bestSellOfferPrice); spread = bestBuyOfferPrice.subtract(bestSellOfferPrice);
// TODO maybe show extra colums with spread and use real amount diff // TODO maybe show extra columns with spread and use real amount diff
// not % based. e.g. diff between best buy and sell offer (of small amounts its a smaller gain) // not % based. e.g. diff between best buy and sell offer (of small amounts its a smaller gain)
if (spread != null && marketPrice != null && marketPrice.isPriceAvailable()) { if (spread != null && marketPrice != null && marketPrice.isPriceAvailable()) {

View file

@ -34,8 +34,7 @@ import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res; import bisq.core.locale.Res;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.monetary.Volume; import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.util.FormattingUtils; import bisq.core.util.FormattingUtils;
import bisq.core.util.coin.CoinFormatter; import bisq.core.util.coin.CoinFormatter;
@ -103,7 +102,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
private final CoinFormatter formatter; private final CoinFormatter formatter;
private TableView<TradeStatistics2> tableView; private TableView<TradeStatistics3> tableView;
private AutocompleteComboBox<CurrencyListItem> currencyComboBox; private AutocompleteComboBox<CurrencyListItem> currencyComboBox;
private VolumeChart volumeChart; private VolumeChart volumeChart;
private CandleStickChart priceChart; private CandleStickChart priceChart;
@ -118,12 +117,12 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
private ChangeListener<Toggle> timeUnitChangeListener; private ChangeListener<Toggle> timeUnitChangeListener;
private ToggleGroup toggleGroup; private ToggleGroup toggleGroup;
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener; private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
private SortedList<TradeStatistics2> sortedList; private SortedList<TradeStatistics3> sortedList;
private Label nrOfTradeStatisticsLabel; private Label nrOfTradeStatisticsLabel;
private ListChangeListener<TradeStatistics2> tradeStatisticsByCurrencyListener; private ListChangeListener<TradeStatistics3> tradeStatisticsByCurrencyListener;
private ChangeListener<Number> selectedTabIndexListener; private ChangeListener<Number> selectedTabIndexListener;
private SingleSelectionModel<Tab> tabPaneSelectionModel; private SingleSelectionModel<Tab> tabPaneSelectionModel;
private TableColumn<TradeStatistics2, TradeStatistics2> priceColumn, volumeColumn, marketColumn; private TableColumn<TradeStatistics3, TradeStatistics3> priceColumn, volumeColumn, marketColumn;
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private MonadicBinding<Void> currencySelectionBinding; private MonadicBinding<Void> currencySelectionBinding;
private Subscription currencySelectionSubscriber; private Subscription currencySelectionSubscriber;
@ -550,7 +549,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
VBox.setVgrow(tableView, Priority.ALWAYS); VBox.setVgrow(tableView, Priority.ALWAYS);
// date // date
TableColumn<TradeStatistics2, TradeStatistics2> dateColumn = new AutoTooltipTableColumn<>(Res.get("shared.dateTime")) { TableColumn<TradeStatistics3, TradeStatistics3> dateColumn = new AutoTooltipTableColumn<>(Res.get("shared.dateTime")) {
{ {
setMinWidth(240); setMinWidth(240);
setMaxWidth(240); setMaxWidth(240);
@ -561,11 +560,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
dateColumn.setCellFactory( dateColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(DisplayUtils.formatDateTime(item.getTradeDate())); setText(DisplayUtils.formatDateTime(item.getTradeDate()));
@ -575,7 +574,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
}; };
} }
}); });
dateColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeDate)); dateColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeDate));
tableView.getColumns().add(dateColumn); tableView.getColumns().add(dateColumn);
// market // market
@ -590,21 +589,21 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
marketColumn.setCellFactory( marketColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(CurrencyUtil.getCurrencyPair(item.getCurrencyCode())); setText(CurrencyUtil.getCurrencyPair(item.getCurrency()));
else else
setText(""); setText("");
} }
}; };
} }
}); });
marketColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeDate)); marketColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeDate));
tableView.getColumns().add(marketColumn); tableView.getColumns().add(marketColumn);
// price // price
@ -614,11 +613,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
priceColumn.setCellFactory( priceColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(FormattingUtils.formatPrice(item.getTradePrice())); setText(FormattingUtils.formatPrice(item.getTradePrice()));
@ -628,21 +627,21 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
}; };
} }
}); });
priceColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradePrice)); priceColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradePrice));
tableView.getColumns().add(priceColumn); tableView.getColumns().add(priceColumn);
// amount // amount
TableColumn<TradeStatistics2, TradeStatistics2> amountColumn = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode())); TableColumn<TradeStatistics3, TradeStatistics3> amountColumn = new AutoTooltipTableColumn<>(Res.get("shared.amountWithCur", Res.getBaseCurrencyCode()));
amountColumn.getStyleClass().add("number-column"); amountColumn.getStyleClass().add("number-column");
amountColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue())); amountColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
amountColumn.setCellFactory( amountColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setGraphic(new ColoredDecimalPlacesWithZerosText(formatter.formatCoin(item.getTradeAmount(), setGraphic(new ColoredDecimalPlacesWithZerosText(formatter.formatCoin(item.getTradeAmount(),
@ -653,7 +652,7 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
}; };
} }
}); });
amountColumn.setComparator(Comparator.comparing(TradeStatistics2::getTradeAmount)); amountColumn.setComparator(Comparator.comparing(TradeStatistics3::getTradeAmount));
tableView.getColumns().add(amountColumn); tableView.getColumns().add(amountColumn);
// volume // volume
@ -663,11 +662,11 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
volumeColumn.setCellFactory( volumeColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(model.showAllTradeCurrenciesProperty.get() ? setText(model.showAllTradeCurrenciesProperty.get() ?
@ -687,17 +686,17 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
tableView.getColumns().add(volumeColumn); tableView.getColumns().add(volumeColumn);
// paymentMethod // paymentMethod
TableColumn<TradeStatistics2, TradeStatistics2> paymentMethodColumn = new AutoTooltipTableColumn<>(Res.get("shared.paymentMethod")); TableColumn<TradeStatistics3, TradeStatistics3> paymentMethodColumn = new AutoTooltipTableColumn<>(Res.get("shared.paymentMethod"));
paymentMethodColumn.getStyleClass().add("number-column"); paymentMethodColumn.getStyleClass().add("number-column");
paymentMethodColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue())); paymentMethodColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
paymentMethodColumn.setCellFactory( paymentMethodColumn.setCellFactory(
new Callback<>() { new Callback<>() {
@Override @Override
public TableCell<TradeStatistics2, TradeStatistics2> call( public TableCell<TradeStatistics3, TradeStatistics3> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) { TableColumn<TradeStatistics3, TradeStatistics3> column) {
return new TableCell<>() { return new TableCell<>() {
@Override @Override
public void updateItem(final TradeStatistics2 item, boolean empty) { public void updateItem(final TradeStatistics3 item, boolean empty) {
super.updateItem(item, empty); super.updateItem(item, empty);
if (item != null) if (item != null)
setText(getPaymentMethodLabel(item)); setText(getPaymentMethodLabel(item));
@ -710,30 +709,6 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
paymentMethodColumn.setComparator(Comparator.comparing(this::getPaymentMethodLabel)); paymentMethodColumn.setComparator(Comparator.comparing(this::getPaymentMethodLabel));
tableView.getColumns().add(paymentMethodColumn); tableView.getColumns().add(paymentMethodColumn);
// direction
TableColumn<TradeStatistics2, TradeStatistics2> directionColumn = new AutoTooltipTableColumn<>(Res.get("shared.offerType"));
directionColumn.getStyleClass().addAll("number-column", "last-column");
directionColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
directionColumn.setCellFactory(
new Callback<>() {
@Override
public TableCell<TradeStatistics2, TradeStatistics2> call(
TableColumn<TradeStatistics2, TradeStatistics2> column) {
return new TableCell<>() {
@Override
public void updateItem(final TradeStatistics2 item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(getDirectionLabel(item));
else
setText("");
}
};
}
});
directionColumn.setComparator(Comparator.comparing(this::getDirectionLabel));
tableView.getColumns().add(directionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new AutoTooltipLabel(Res.get("table.placeholder.noData")); Label placeholder = new AutoTooltipLabel(Res.get("table.placeholder.noData"));
placeholder.setWrapText(true); placeholder.setWrapText(true);
@ -743,13 +718,8 @@ public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesCharts
} }
@NotNull @NotNull
private String getDirectionLabel(TradeStatistics2 item) { private String getPaymentMethodLabel(TradeStatistics3 item) {
return DisplayUtils.getDirectionWithCode(OfferPayload.Direction.valueOf(item.getDirection().name()), item.getCurrencyCode()); return Res.get(item.getPaymentMethod());
}
@NotNull
private String getPaymentMethodLabel(TradeStatistics2 item) {
return Res.get(item.getOfferPaymentMethod());
} }
private void layout() { private void layout() {

View file

@ -34,7 +34,7 @@ import bisq.core.locale.GlobalSettings;
import bisq.core.locale.TradeCurrency; import bisq.core.locale.TradeCurrency;
import bisq.core.monetary.Altcoin; import bisq.core.monetary.Altcoin;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
@ -97,18 +97,18 @@ class TradesChartsViewModel extends ActivatableViewModel {
private final TradeStatisticsManager tradeStatisticsManager; private final TradeStatisticsManager tradeStatisticsManager;
final Preferences preferences; final Preferences preferences;
private PriceFeedService priceFeedService; private final PriceFeedService priceFeedService;
private Navigation navigation; private final Navigation navigation;
private final SetChangeListener<TradeStatistics2> setChangeListener; private final SetChangeListener<TradeStatistics3> setChangeListener;
final ObjectProperty<TradeCurrency> selectedTradeCurrencyProperty = new SimpleObjectProperty<>(); final ObjectProperty<TradeCurrency> selectedTradeCurrencyProperty = new SimpleObjectProperty<>();
final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(false); final BooleanProperty showAllTradeCurrenciesProperty = new SimpleBooleanProperty(false);
private final CurrencyList currencyListItems; private final CurrencyList currencyListItems;
private final CurrencyListItem showAllCurrencyListItem = new CurrencyListItem(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""), -1); private final CurrencyListItem showAllCurrencyListItem = new CurrencyListItem(new CryptoCurrency(GUIUtil.SHOW_ALL_FLAG, ""), -1);
final ObservableList<TradeStatistics2> tradeStatisticsByCurrency = FXCollections.observableArrayList(); final ObservableList<TradeStatistics3> tradeStatisticsByCurrency = FXCollections.observableArrayList();
final ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList(); final ObservableList<XYChart.Data<Number, Number>> priceItems = FXCollections.observableArrayList();
final ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList(); final ObservableList<XYChart.Data<Number, Number>> volumeItems = FXCollections.observableArrayList();
private Map<Long, Pair<Date, Set<TradeStatistics2>>> itemsPerInterval; private Map<Long, Pair<Date, Set<TradeStatistics3>>> itemsPerInterval;
TickUnit tickUnit; TickUnit tickUnit;
final int maxTicks = 90; final int maxTicks = 90;
@ -119,7 +119,8 @@ class TradesChartsViewModel extends ActivatableViewModel {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences, PriceFeedService priceFeedService, Navigation navigation) { TradesChartsViewModel(TradeStatisticsManager tradeStatisticsManager, Preferences preferences,
PriceFeedService priceFeedService, Navigation navigation) {
this.tradeStatisticsManager = tradeStatisticsManager; this.tradeStatisticsManager = tradeStatisticsManager;
this.preferences = preferences; this.preferences = preferences;
this.priceFeedService = priceFeedService; this.priceFeedService = priceFeedService;
@ -145,7 +146,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
private void fillTradeCurrencies() { private void fillTradeCurrencies() {
// Don't use a set as we need all entries // Don't use a set as we need all entries
List<TradeCurrency> tradeCurrencyList = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() List<TradeCurrency> tradeCurrencyList = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.flatMap(e -> CurrencyUtil.getTradeCurrency(e.getCurrencyCode()).stream()) .flatMap(e -> CurrencyUtil.getTradeCurrency(e.getCurrency()).stream())
.collect(Collectors.toList()); .collect(Collectors.toList());
currencyListItems.updateWithCurrencies(tradeCurrencyList, showAllCurrencyListItem); currencyListItems.updateWithCurrencies(tradeCurrencyList, showAllCurrencyListItem);
@ -241,15 +242,15 @@ class TradesChartsViewModel extends ActivatableViewModel {
private void updateChartData() { private void updateChartData() {
tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream() tradeStatisticsByCurrency.setAll(tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrencyCode().equals(getCurrencyCode())) .filter(e -> showAllTradeCurrenciesProperty.get() || e.getCurrency().equals(getCurrencyCode()))
.collect(Collectors.toList())); .collect(Collectors.toList()));
// Generate date range and create sets for all ticks // Generate date range and create sets for all ticks
itemsPerInterval = new HashMap<>(); itemsPerInterval = new HashMap<>();
Date time = new Date(); Date time = new Date();
for (long i = maxTicks + 1; i >= 0; --i) { for (long i = maxTicks + 1; i >= 0; --i) {
Set<TradeStatistics2> set = new HashSet<>(); Set<TradeStatistics3> set = new HashSet<>();
Pair<Date, Set<TradeStatistics2>> pair = new Pair<>((Date) time.clone(), set); Pair<Date, Set<TradeStatistics3>> pair = new Pair<>((Date) time.clone(), set);
itemsPerInterval.put(i, pair); itemsPerInterval.put(i, pair);
time.setTime(time.getTime() - 1); time.setTime(time.getTime() - 1);
time = roundToTick(time, tickUnit); time = roundToTick(time, tickUnit);
@ -258,7 +259,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
// Get all entries for the defined time interval // Get all entries for the defined time interval
tradeStatisticsByCurrency.forEach(e -> { tradeStatisticsByCurrency.forEach(e -> {
for (long i = maxTicks; i > 0; --i) { for (long i = maxTicks; i > 0; --i) {
Pair<Date, Set<TradeStatistics2>> p = itemsPerInterval.get(i); Pair<Date, Set<TradeStatistics3>> p = itemsPerInterval.get(i);
if (e.getTradeDate().after(p.getKey())) { if (e.getTradeDate().after(p.getKey())) {
p.getValue().add(e); p.getValue().add(e);
break; break;
@ -283,7 +284,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
} }
@VisibleForTesting @VisibleForTesting
CandleData getCandleData(long tick, Set<TradeStatistics2> set) { CandleData getCandleData(long tick, Set<TradeStatistics3> set) {
long open = 0; long open = 0;
long close = 0; long close = 0;
long high = 0; long high = 0;
@ -293,7 +294,7 @@ class TradesChartsViewModel extends ActivatableViewModel {
long numTrades = set.size(); long numTrades = set.size();
List<Long> tradePrices = new ArrayList<>(set.size()); List<Long> tradePrices = new ArrayList<>(set.size());
for (TradeStatistics2 item : set) { for (TradeStatistics3 item : set) {
long tradePriceAsLong = item.getTradePrice().getValue(); long tradePriceAsLong = item.getTradePrice().getValue();
// Previously a check was done which inverted the low and high for cryptocurrencies. // Previously a check was done which inverted the low and high for cryptocurrencies.
low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong; low = (low != 0) ? Math.min(low, tradePriceAsLong) : tradePriceAsLong;
@ -305,8 +306,8 @@ class TradesChartsViewModel extends ActivatableViewModel {
} }
Collections.sort(tradePrices); Collections.sort(tradePrices);
List<TradeStatistics2> list = new ArrayList<>(set); List<TradeStatistics3> list = new ArrayList<>(set);
list.sort(Comparator.comparingLong(o -> o.getTradeDate().getTime())); list.sort(Comparator.comparingLong(TradeStatistics3::getDate));
if (list.size() > 0) { if (list.size() > 0) {
open = list.get(0).getTradePrice().getValue(); open = list.get(0).getTradePrice().getValue();
close = list.get(list.size() - 1).getTradePrice().getValue(); close = list.get(list.size() - 1).getTradePrice().getValue();

View file

@ -43,7 +43,7 @@ import bisq.core.payment.PaymentAccount;
import bisq.core.provider.fee.FeeService; import bisq.core.provider.fee.FeeService;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.handlers.TransactionResultHandler; import bisq.core.trade.handlers.TransactionResultHandler;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.core.user.User; import bisq.core.user.User;
@ -344,9 +344,9 @@ public abstract class MutableOfferDataModel extends OfferDataModel implements Bs
var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset()); var blocksRange = Restrictions.getLockTime(paymentAccount.getPaymentMethod().isAsset());
var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000); var startDate = new Date(System.currentTimeMillis() - blocksRange * 10 * 60000);
var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream() var sortedRangeData = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals(getTradeCurrency().getCode())) .filter(e -> e.getCurrency().equals(getTradeCurrency().getCode()))
.filter(e -> e.getTradeDate().compareTo(startDate) >= 0) .filter(e -> e.getTradeDate().compareTo(startDate) >= 0)
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate)) .sorted(Comparator.comparing(TradeStatistics3::getTradeDate))
.collect(Collectors.toList()); .collect(Collectors.toList());
var movingAverage = new MathUtils.MovingAverage(10, 0.2); var movingAverage = new MathUtils.MovingAverage(10, 0.2);
double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE}; double[] extremes = {Double.MAX_VALUE, Double.MIN_VALUE};

View file

@ -332,7 +332,7 @@ public class FormBuilder {
final Tuple2<Label, VBox> topLabelWithVBox = addTopLabelWithVBox(gridPane, rowIndex, title, textField, top); final Tuple2<Label, VBox> topLabelWithVBox = addTopLabelWithVBox(gridPane, rowIndex, title, textField, top);
// TOD not 100% sure if that is a good idea.... // TODO not 100% sure if that is a good idea....
//topLabelWithVBox.first.getStyleClass().add("jfx-text-field-top-label"); //topLabelWithVBox.first.getStyleClass().add("jfx-text-field-top-label");
return new Tuple3<>(topLabelWithVBox.first, textField, topLabelWithVBox.second); return new Tuple3<>(topLabelWithVBox.first, textField, topLabelWithVBox.second);

View file

@ -11,7 +11,6 @@
</root> </root>
<logger name="com.neemre.btcdcli4j" level="WARN"/> <logger name="com.neemre.btcdcli4j" level="WARN"/>
<!-- <logger name="bisq.core.provider" level="WARN"/> <logger name="com.neemre.btcdcli4j.core.client.ClientConfigurator" level="ERROR"/>
<logger name="bisq.network" level="WARN"/>-->
</configuration> </configuration>

View file

@ -23,14 +23,12 @@ import bisq.desktop.main.market.trades.charts.CandleData;
import bisq.core.locale.FiatCurrency; import bisq.core.locale.FiatCurrency;
import bisq.core.monetary.Price; import bisq.core.monetary.Price;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.provider.price.PriceFeedService; import bisq.core.provider.price.PriceFeedService;
import bisq.core.trade.statistics.TradeStatistics2; import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager; import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.user.Preferences; import bisq.core.user.Preferences;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import org.bitcoinj.core.Coin; import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat; import org.bitcoinj.utils.Fiat;
@ -49,9 +47,6 @@ import java.util.Date;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
@ -63,9 +58,7 @@ public class TradesChartsViewModelTest {
TradesChartsViewModel model; TradesChartsViewModel model;
TradeStatisticsManager tradeStatisticsManager; TradeStatisticsManager tradeStatisticsManager;
private static final Logger log = LoggerFactory.getLogger(TradesChartsViewModelTest.class);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private KeyRing keyRing;
private File dir; private File dir;
OfferPayload offer = new OfferPayload(null, OfferPayload offer = new OfferPayload(null,
0, 0,
@ -106,6 +99,7 @@ public class TradesChartsViewModelTest {
null, null,
1 1
); );
@Before @Before
public void setup() throws IOException { public void setup() throws IOException {
tradeStatisticsManager = mock(TradeStatisticsManager.class); tradeStatisticsManager = mock(TradeStatisticsManager.class);
@ -116,7 +110,6 @@ public class TradesChartsViewModelTest {
dir.delete(); dir.delete();
//noinspection ResultOfMethodCallIgnored //noinspection ResultOfMethodCallIgnored
dir.mkdir(); dir.mkdir();
keyRing = new KeyRing(new KeyStorage(dir));
} }
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
@ -134,13 +127,45 @@ public class TradesChartsViewModelTest {
long volume = Fiat.parseFiat("EUR", "2200").value; long volume = Fiat.parseFiat("EUR", "2200").value;
boolean isBullish = true; boolean isBullish = true;
Set<TradeStatistics2> set = new HashSet<>(); Set<TradeStatistics3> set = new HashSet<>();
final Date now = new Date(); final Date now = new Date();
set.add(new TradeStatistics2(offer, Price.parse("EUR", "520"), Coin.parseCoin("1"), new Date(now.getTime()), null, null)); set.add(new TradeStatistics3(offer.getCurrencyCode(),
set.add(new TradeStatistics2(offer, Price.parse("EUR", "500"), Coin.parseCoin("1"), new Date(now.getTime() + 100), null, null)); Price.parse("EUR", "520").getValue(),
set.add(new TradeStatistics2(offer, Price.parse("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null)); Coin.parseCoin("1").getValue(),
set.add(new TradeStatistics2(offer, Price.parse("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null)); PaymentMethod.BLOCK_CHAINS_ID,
now.getTime(),
null,
null,
null,
null));
set.add(new TradeStatistics3(offer.getCurrencyCode(),
Price.parse("EUR", "500").getValue(),
Coin.parseCoin("1").getValue(),
PaymentMethod.BLOCK_CHAINS_ID,
now.getTime() + 100,
null,
null,
null,
null));
set.add(new TradeStatistics3(offer.getCurrencyCode(),
Price.parse("EUR", "600").getValue(),
Coin.parseCoin("1").getValue(),
PaymentMethod.BLOCK_CHAINS_ID,
now.getTime() + 200,
null,
null,
null,
null));
set.add(new TradeStatistics3(offer.getCurrencyCode(),
Price.parse("EUR", "580").getValue(),
Coin.parseCoin("1").getValue(),
PaymentMethod.BLOCK_CHAINS_ID,
now.getTime() + 300,
null,
null,
null,
null));
CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set); CandleData candleData = model.getCandleData(model.roundToTick(now, TradesChartsViewModel.TickUnit.DAY).getTime(), set);
assertEquals(open, candleData.open); assertEquals(open, candleData.open);
@ -183,7 +208,6 @@ public class TradesChartsViewModelTest {
ArrayList<Trade> trades = new ArrayList<>(); ArrayList<Trade> trades = new ArrayList<>();
// Set predetermined time to use as "now" during test // Set predetermined time to use as "now" during test
Date test_time = dateFormat.parse("2018-01-01T00:00:05"); // Monday
/* new MockUp<System>() { /* new MockUp<System>() {
@Mock @Mock
long currentTimeMillis() { long currentTimeMillis() {
@ -194,11 +218,19 @@ public class TradesChartsViewModelTest {
// Two trades 10 seconds apart, different YEAR, MONTH, WEEK, DAY, HOUR, MINUTE_10 // Two trades 10 seconds apart, different YEAR, MONTH, WEEK, DAY, HOUR, MINUTE_10
trades.add(new Trade("2017-12-31T23:59:52", "1", "100", "EUR")); trades.add(new Trade("2017-12-31T23:59:52", "1", "100", "EUR"));
trades.add(new Trade("2018-01-01T00:00:02", "1", "110", "EUR")); trades.add(new Trade("2018-01-01T00:00:02", "1", "110", "EUR"));
Set<TradeStatistics2> set = new HashSet<>(); Set<TradeStatistics3> set = new HashSet<>();
trades.forEach(t -> trades.forEach(t ->
set.add(new TradeStatistics2(offer, Price.parse(t.cc, t.price), Coin.parseCoin(t.size), t.date, null, null)) set.add(new TradeStatistics3(offer.getCurrencyCode(),
Price.parse(t.cc, t.price).getValue(),
Coin.parseCoin(t.size).getValue(),
PaymentMethod.BLOCK_CHAINS_ID,
t.date.getTime(),
null,
null,
null,
null))
); );
ObservableSet<TradeStatistics2> tradeStats = FXCollections.observableSet(set); ObservableSet<TradeStatistics3> tradeStats = FXCollections.observableSet(set);
// Run test for each tick type // Run test for each tick type
for (TradesChartsViewModel.TickUnit tick : TradesChartsViewModel.TickUnit.values()) { for (TradesChartsViewModel.TickUnit tick : TradesChartsViewModel.TickUnit.values()) {
@ -209,7 +241,7 @@ public class TradesChartsViewModelTest {
// Trigger chart update // Trigger chart update
model.setTickUnit(tick); model.setTickUnit(tick);
assertEquals(model.selectedTradeCurrencyProperty.get().getCode(), tradeStats.iterator().next().getCurrencyCode()); assertEquals(model.selectedTradeCurrencyProperty.get().getCode(), tradeStats.iterator().next().getCurrency());
assertEquals(2, model.priceItems.size()); assertEquals(2, model.priceItems.size());
assertEquals(2, model.volumeItems.size()); assertEquals(2, model.volumeItems.size());
} }

View file

@ -90,7 +90,8 @@ public class Monitor {
Capability.DAO_STATE, Capability.DAO_STATE,
Capability.BUNDLE_OF_ENVELOPES, Capability.BUNDLE_OF_ENVELOPES,
Capability.REFUND_AGENT, Capability.REFUND_AGENT,
Capability.MEDIATION); Capability.MEDIATION,
Capability.TRADE_STATISTICS_3);
// assemble Metrics // assemble Metrics
// - create reporters // - create reporters

View file

@ -20,30 +20,15 @@ package bisq.monitor.metric;
import bisq.monitor.Metric; import bisq.monitor.Metric;
import bisq.monitor.Reporter; import bisq.monitor.Reporter;
import bisq.asset.Asset;
import bisq.asset.AssetRegistry;
import bisq.network.p2p.storage.payload.ProtectedStoragePayload;
import org.berndpruenster.netlayer.tor.TorCtlException;
import com.runjva.sourceforge.jsocks.protocol.SocksSocket;
import java.net.HttpURLConnection;
import java.net.Socket;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -110,7 +95,6 @@ public class MarketStats extends Metric {
} catch (IllegalStateException ignore) { } catch (IllegalStateException ignore) {
// no match found // no match found
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }

View file

@ -23,7 +23,7 @@ import bisq.monitor.Reporter;
import bisq.core.account.witness.AccountAgeWitnessStore; import bisq.core.account.witness.AccountAgeWitnessStore;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.core.trade.statistics.TradeStatistics2Store; import bisq.core.trade.statistics.TradeStatistics3Store;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.Connection;
@ -163,8 +163,8 @@ public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString(); String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
try { try {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null); PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix); TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix); AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));

View file

@ -27,7 +27,7 @@ import bisq.core.dao.monitoring.network.messages.GetDaoStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest; import bisq.core.dao.monitoring.network.messages.GetProposalStateHashesRequest;
import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse; import bisq.core.dao.monitoring.network.messages.GetStateHashesResponse;
import bisq.core.proto.persistable.CorePersistenceProtoResolver; import bisq.core.proto.persistable.CorePersistenceProtoResolver;
import bisq.core.trade.statistics.TradeStatistics2Store; import bisq.core.trade.statistics.TradeStatistics3Store;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.network.Connection; import bisq.network.p2p.network.Connection;
@ -137,8 +137,8 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString(); String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
try { try {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null); PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix); TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix); AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList())); hashes.addAll(accountAgeWitnessStore.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
@ -233,9 +233,9 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
int oldest = (int) nodeAddressTupleMap.values().stream().min(Comparator.comparingLong(Tuple::getHeight)).get().height; int oldest = (int) nodeAddressTupleMap.values().stream().min(Comparator.comparingLong(Tuple::getHeight)).get().height;
// - update queried height // - update queried height
if(type.contains("DaoState")) if (type.contains("DaoState"))
daostateheight = oldest - 20; daostateheight = oldest - 20;
else if(type.contains("Proposal")) else if (type.contains("Proposal"))
proposalheight = oldest - 20; proposalheight = oldest - 20;
else else
blindvoteheight = oldest - 20; blindvoteheight = oldest - 20;
@ -255,7 +255,6 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
List<ByteBuffer> states = hitcount.entrySet().stream().sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())).map(byteBufferIntegerEntry -> byteBufferIntegerEntry.getKey()).collect(Collectors.toList()); List<ByteBuffer> states = hitcount.entrySet().stream().sorted((o1, o2) -> o2.getValue().compareTo(o1.getValue())).map(byteBufferIntegerEntry -> byteBufferIntegerEntry.getKey()).collect(Collectors.toList());
hitcount.clear(); hitcount.clear();
nodeAddressTupleMap.forEach((nodeAddress, tuple) -> daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".hash", Integer.toString(Arrays.asList(states.toArray()).indexOf(ByteBuffer.wrap(tuple.hash))))); nodeAddressTupleMap.forEach((nodeAddress, tuple) -> daoreport.put(type + "." + OnionParser.prettyPrint(nodeAddress) + ".hash", Integer.toString(Arrays.asList(states.toArray()).indexOf(ByteBuffer.wrap(tuple.hash)))));
// - report reference head // - report reference head

View file

@ -159,7 +159,6 @@ public class PriceNodeStats extends Metric {
} }
} }
} catch (TorCtlException | IOException e) { } catch (TorCtlException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }

View file

@ -21,6 +21,7 @@ import bisq.monitor.Metric;
import bisq.monitor.OnionParser; import bisq.monitor.OnionParser;
import bisq.monitor.Reporter; import bisq.monitor.Reporter;
import bisq.monitor.StatisticsHelper; import bisq.monitor.StatisticsHelper;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import org.berndpruenster.netlayer.tor.Tor; import org.berndpruenster.netlayer.tor.Tor;
@ -33,6 +34,7 @@ import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
/** /**
@ -84,7 +86,6 @@ public class TorRoundTripTime extends Metric {
reporter.report(StatisticsHelper.process(samples), getName()); reporter.report(StatisticsHelper.process(samples), getName());
} }
} catch (TorCtlException | IOException e) { } catch (TorCtlException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }

View file

@ -58,7 +58,6 @@ public class TorStartupTime extends Metric {
try { try {
torOverrides = new Torrc(overrides); torOverrides = new Torrc(overrides);
} catch (IOException e) { } catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
@ -79,7 +78,6 @@ public class TorStartupTime extends Metric {
// stop the timer and set its timestamp // stop the timer and set its timestamp
reporter.report(System.currentTimeMillis() - start, getName()); reporter.report(System.currentTimeMillis() - start, getName());
} catch (TorCtlException e) { } catch (TorCtlException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
// cleanup // cleanup

View file

@ -920,6 +920,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
return keyRing; return keyRing;
} }
public Optional<Capabilities> findPeersCapabilities(NodeAddress peer) {
return networkNode.getConfirmedConnections().stream()
.filter(e -> e.getPeersNodeAddressOptional().isPresent())
.filter(e -> e.getPeersNodeAddressOptional().get().equals(peer))
.map(Connection::getCapabilities)
.findAny();
}
@Value @Value
public class MailboxItem { public class MailboxItem {

View file

@ -61,7 +61,7 @@ public abstract class MapStoreService<T extends PersistableEnvelope, R extends P
requestPersistence(); requestPersistence();
} }
R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) { protected R putIfAbsent(P2PDataStorage.ByteArray hash, R payload) {
R previous = getMap().putIfAbsent(hash, payload); R previous = getMap().putIfAbsent(hash, payload);
requestPersistence(); requestPersistence();
return previous; return previous;

Binary file not shown.

Binary file not shown.

View file

@ -154,7 +154,7 @@ message GetTradeStatisticsRequest {
} }
message GetTradeStatisticsReply { message GetTradeStatisticsReply {
repeated TradeStatistics2 TradeStatistics = 1; repeated TradeStatistics3 TradeStatistics = 1;
} }
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -510,9 +510,7 @@ message StoragePayload {
Mediator mediator = 3; Mediator mediator = 3;
Filter filter = 4; Filter filter = 4;
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older // TradeStatistics trade_statistics = 5 [deprecated = true]; Removed in v.1.4.0
// versions and convert it to TradeStatistics2 objects.
TradeStatistics trade_statistics = 5 [deprecated = true];
MailboxStoragePayload mailbox_storage_payload = 6; MailboxStoragePayload mailbox_storage_payload = 6;
OfferPayload offer_payload = 7; OfferPayload offer_payload = 7;
@ -524,10 +522,11 @@ message StoragePayload {
message PersistableNetworkPayload { message PersistableNetworkPayload {
oneof message { oneof message {
AccountAgeWitness account_age_witness = 1; AccountAgeWitness account_age_witness = 1;
TradeStatistics2 trade_statistics2 = 2; TradeStatistics2 trade_statistics2 = 2 [deprecated = true];
ProposalPayload proposal_payload = 3; ProposalPayload proposal_payload = 3;
BlindVotePayload blind_vote_payload = 4; BlindVotePayload blind_vote_payload = 4;
SignedWitness signed_witness = 5; SignedWitness signed_witness = 5;
TradeStatistics3 trade_statistics3 = 6;
} }
} }
@ -650,44 +649,36 @@ message Filter {
bool disable_auto_conf = 24; bool disable_auto_conf = 24;
} }
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older // Deprecated
// versions and convert it to TradeStatistics2 objects. message TradeStatistics2 {
message TradeStatistics { string base_currency = 1 [deprecated = true];
string base_currency = 1; string counter_currency = 2 [deprecated = true];
string counter_currency = 2; OfferPayload.Direction direction = 3 [deprecated = true];
OfferPayload.Direction direction = 3; int64 trade_price = 4 [deprecated = true];
int64 trade_price = 4; int64 trade_amount = 5 [deprecated = true];
int64 trade_amount = 5; int64 trade_date = 6 [deprecated = true];
int64 trade_date = 6; string payment_method_id = 7 [deprecated = true];
string payment_method_id = 7; int64 offer_date = 8 [deprecated = true];
int64 offer_date = 8; bool offer_use_market_based_price = 9 [deprecated = true];
bool offer_use_market_based_price = 9; double offer_market_price_margin = 10 [deprecated = true];
double offer_market_price_margin = 10; int64 offer_amount = 11 [deprecated = true];
int64 offer_amount = 11; int64 offer_min_amount = 12 [deprecated = true];
int64 offer_min_amount = 12; string offer_id = 13 [deprecated = true];
string offer_id = 13; string deposit_tx_id = 14 [deprecated = true];
string deposit_tx_id = 14; bytes hash = 15 [deprecated = true];
bytes signature_pub_key_bytes = 15; map<string, string> extra_data = 16 [deprecated = true];
map<string, string> extra_data = 16;
} }
message TradeStatistics2 { message TradeStatistics3 {
string base_currency = 1; string currency = 1;
string counter_currency = 2; int64 price = 2;
OfferPayload.Direction direction = 3; int64 amount = 3;
int64 trade_price = 4; string payment_method = 4;
int64 trade_amount = 5; int64 date = 5;
int64 trade_date = 6; string mediator = 6;
string payment_method_id = 7; string refund_agent = 7;
int64 offer_date = 8; bytes hash = 8;
bool offer_use_market_based_price = 9; map<string, string> extra_data = 9;
double offer_market_price_margin = 10;
int64 offer_amount = 11;
int64 offer_min_amount = 12;
string offer_id = 13;
string deposit_tx_id = 14;
bytes hash = 15;
map<string, string> extra_data = 16;
} }
message MailboxStoragePayload { message MailboxStoragePayload {
@ -1152,7 +1143,7 @@ message PersistableEnvelope {
// BsqState bsq_state = 12; // not used but as other non-dao data have a higher index number we leave it to make clear that we cannot change following indexes // BsqState bsq_state = 12; // not used but as other non-dao data have a higher index number we leave it to make clear that we cannot change following indexes
AccountAgeWitnessStore account_age_witness_store = 13; AccountAgeWitnessStore account_age_witness_store = 13;
TradeStatistics2Store trade_statistics2_store = 14; TradeStatistics2Store trade_statistics2_store = 14 [deprecated = true];
// PersistableNetworkPayloadList persistable_network_payload_list = 15; // long deprecated & migration away from it is already done // PersistableNetworkPayloadList persistable_network_payload_list = 15; // long deprecated & migration away from it is already done
@ -1171,6 +1162,7 @@ message PersistableEnvelope {
SignedWitnessStore signed_witness_store = 28; SignedWitnessStore signed_witness_store = 28;
MediationDisputeList mediation_dispute_list = 29; MediationDisputeList mediation_dispute_list = 29;
RefundDisputeList refund_dispute_list = 30; RefundDisputeList refund_dispute_list = 30;
TradeStatistics3Store trade_statistics3_store = 31;
} }
} }
@ -1211,8 +1203,13 @@ message SignedWitnessStore {
} }
// We use a list not a hash map to save disc space. The hash can be calculated from the payload anyway // We use a list not a hash map to save disc space. The hash can be calculated from the payload anyway
// Deprecated
message TradeStatistics2Store { message TradeStatistics2Store {
repeated TradeStatistics2 items = 1; repeated TradeStatistics2 items = 1 [deprecated = true];
}
message TradeStatistics3Store {
repeated TradeStatistics3 items = 1;
} }
message PeerList { message PeerList {