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
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.
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 REIMBURSEMENT_REQUEST = (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
* 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 {
}

View file

@ -48,7 +48,6 @@ import java.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO is Hmac needed/make sense?
public class Encryption {
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;
// TODO: use a password protection for key storage
@Singleton
public class KeyStorage {
private static final Logger log = LoggerFactory.getLogger(KeyStorage.class);

View file

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

View file

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

View file

@ -22,7 +22,7 @@ import bisq.core.monetary.Price;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
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.common.app.Version;
@ -196,7 +196,7 @@ public class CoreApi {
walletsService.removeWalletPassword(password);
}
public List<TradeStatistics2> getTradeStatistics() {
public List<TradeStatistics3> getTradeStatistics() {
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.CoreSetup;
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.core.trade.txproof.xmr.XmrTxProofService;
import bisq.network.p2p.P2PService;
@ -220,6 +221,7 @@ public abstract class BisqExecutable implements GracefulShutDownHandler, BisqSet
try {
injector.getInstance(ArbitratorManager.class).shutDown();
injector.getInstance(TradeStatisticsManager.class).shutDown();
injector.getInstance(XmrTxProofService.class).shutDown();
injector.getInstance(DaoSetup.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.
Map<String, List<TradeAmountDateTuple>> lookupMap = new HashMap<>();
tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> CurrencyUtil.isCryptoCurrency(e.getBaseCurrency()))
.filter(e -> CurrencyUtil.isCryptoCurrency(e.getCurrency()))
.forEach(e -> {
lookupMap.putIfAbsent(e.getBaseCurrency(), new ArrayList<>());
lookupMap.get(e.getBaseCurrency()).add(new TradeAmountDateTuple(e.getTradeAmount().getValue(), e.getTradeDate().getTime()));
lookupMap.putIfAbsent(e.getCurrency(), new ArrayList<>());
lookupMap.get(e.getCurrency()).add(new TradeAmountDateTuple(e.getAmount(), e.getDate()));
});
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.DisputeAgentManager;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.core.trade.statistics.TradeStatisticsManager;
import bisq.common.util.Tuple2;
@ -42,36 +42,37 @@ import static com.google.common.base.Preconditions.checkArgument;
@Slf4j
public class DisputeAgentSelection {
public static final int LOOK_BACK_RANGE = 100;
public static <T extends DisputeAgent> T getLeastUsedMediator(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
TradeStatistics2.MEDIATOR_ADDRESS);
true);
}
public static <T extends DisputeAgent> T getLeastUsedRefundAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager) {
return getLeastUsedDisputeAgent(tradeStatisticsManager,
disputeAgentManager,
TradeStatistics2.REFUND_AGENT_ADDRESS);
false);
}
private static <T extends DisputeAgent> T getLeastUsedDisputeAgent(TradeStatisticsManager tradeStatisticsManager,
DisputeAgentManager<T> disputeAgentManager,
String extraMapKey) {
boolean isMediator) {
// We take last 100 entries from trade statistics
List<TradeStatistics2> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics2::getTradeDate));
List<TradeStatistics3> list = new ArrayList<>(tradeStatisticsManager.getObservableTradeStatisticsSet());
list.sort(Comparator.comparing(TradeStatistics3::getDate));
Collections.reverse(list);
if (!list.isEmpty()) {
int max = Math.min(list.size(), 100);
int max = Math.min(list.size(), LOOK_BACK_RANGE);
list = list.subList(0, max);
}
// We stored only first 4 chars of disputeAgents onion address
List<String> lastAddressesUsedInTrades = list.stream()
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null)
.map(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(extraMapKey))
.map(tradeStatistics3 -> isMediator ? tradeStatistics3.getMediator() : tradeStatistics3.getRefundAgent())
.filter(Objects::nonNull)
.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.WesternUnionAccountPayload;
import bisq.core.trade.statistics.TradeStatistics2;
import bisq.core.trade.statistics.TradeStatistics3;
import bisq.common.proto.ProtoResolver;
import bisq.common.proto.ProtobufferRuntimeException;
@ -178,6 +179,8 @@ public class CoreProtoResolver implements ProtoResolver {
return BlindVotePayload.fromProto(proto.getBlindVotePayload());
case SIGNED_WITNESS:
return SignedWitness.fromProto(proto.getSignedWitness());
case TRADE_STATISTICS3:
return TradeStatistics3.fromProto(proto.getTradeStatistics3());
default:
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.RefreshTradeStateRequest;
import bisq.core.trade.messages.TraderSignedWitnessMessage;
import bisq.core.trade.statistics.TradeStatistics;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.BundleOfEnvelopes;
@ -265,9 +264,6 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
return RefundAgent.fromProto(proto.getRefundAgent());
case FILTER:
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:
return MailboxStoragePayload.fromProto(proto.getMailboxStoragePayload());
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.trade.TradableList;
import bisq.core.trade.statistics.TradeStatistics2Store;
import bisq.core.trade.statistics.TradeStatistics3Store;
import bisq.core.user.PreferencesPayload;
import bisq.core.user.UserPayload;
@ -126,6 +127,8 @@ public class CorePersistenceProtoResolver extends CoreProtoResolver implements P
return UnconfirmedBsqChangeOutputList.fromProto(proto.getUnconfirmedBsqChangeOutputList());
case SIGNED_WITNESS_STORE:
return SignedWitnessStore.fromProto(proto.getSignedWitnessStore());
case TRADE_STATISTICS3_STORE:
return TradeStatistics3Store.fromProto(proto.getTradeStatistics3Store());
default:
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;
// TODO use dao parameters for fee
@Slf4j
public class FeeService {

View file

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

View file

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

View file

@ -157,7 +157,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
@Override
protected String getDisputeInfo(Dispute dispute) {
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);
}

View file

@ -24,8 +24,6 @@ import bisq.core.account.witness.AccountAgeWitnessStorageService;
import bisq.core.trade.closed.ClosedTradableManager;
import bisq.core.trade.failed.FailedTradesManager;
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.config.Config;
@ -46,8 +44,6 @@ public class TradeModule extends AppModule {
@Override
protected void configure() {
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(FailedTradesManager.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.TradeMessage;
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.buyer.BuyerProcessDepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessPayoutTxPublishedMessage;
@ -114,8 +113,7 @@ public abstract class BuyerProtocol extends DisputeProtocol {
removeMailboxMessageAfterProcessing(message);
}))
.setup(tasks(BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class,
PublishTradeStatistics.class)
BuyerVerifiesFinalDelayedPayoutTx.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();

View file

@ -23,13 +23,13 @@ import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DelayedPayoutTxSignatureResponse;
import bisq.core.trade.messages.TradeMessage;
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.seller.SellerBroadcastPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller.SellerProcessCounterCurrencyTransferStartedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerProcessDelayedPayoutTxSignatureResponse;
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.SellerSendsDepositTxAndDelayedPayoutTxMessage;
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.ResultHandler;
import bisq.common.util.Utilities;
import java.util.Date;
import java.util.GregorianCalendar;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public abstract class SellerProtocol extends DisputeProtocol {
enum SellerEvent implements FluentProtocol.Event {
STARTUP,
PAYMENT_RECEIVED
}
@ -52,6 +57,27 @@ public abstract class SellerProtocol extends DisputeProtocol {
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
@ -79,7 +105,7 @@ public abstract class SellerProtocol extends DisputeProtocol {
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class,
PublishTradeStatistics.class))
SellerPublishesTradeStatistics.class))
.run(() -> {
// We stop timeout here and don't start a new one as the
// 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
*/
@Deprecated
@Slf4j
@Value
public final class TradeStatistics2 implements ProcessOncePersistableNetworkPayload, PersistableNetworkPayload,

View file

@ -29,6 +29,7 @@ import javax.inject.Named;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
@ -65,11 +66,29 @@ public class TradeStatistics2StorageService extends MapStoreService<TradeStatist
@Override
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() {
return getMap();
return store.getMap();
}
@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.monetary.Price;
import bisq.core.monetary.Volume;
import bisq.core.offer.OfferPayload;
import bisq.common.util.MathUtils;
@ -38,65 +37,44 @@ import javax.annotation.concurrent.Immutable;
@ToString
@Slf4j
public final class TradeStatisticsForJson {
public final String currency;
public final OfferPayload.Direction direction;
public final long tradePrice;
public final long tradeAmount;
public final long tradeDate;
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)
public String currencyPair;
public OfferPayload.Direction primaryMarketDirection;
public long primaryMarketTradePrice;
public long primaryMarketTradeAmount;
public long primaryMarketTradeVolume;
public TradeStatisticsForJson(TradeStatistics2 tradeStatistics) {
this.direction = OfferPayload.Direction.valueOf(tradeStatistics.getDirection().name());
this.currency = tradeStatistics.getCurrencyCode();
this.paymentMethod = tradeStatistics.getOfferPaymentMethod();
this.offerDate = tradeStatistics.getOfferDate();
this.useMarketBasedPrice = tradeStatistics.isOfferUseMarketBasedPrice();
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();
public TradeStatisticsForJson(TradeStatistics3 tradeStatistics) {
this.currency = tradeStatistics.getCurrency();
this.paymentMethod = tradeStatistics.getPaymentMethod();
this.tradePrice = tradeStatistics.getPrice();
this.tradeAmount = tradeStatistics.getAmount();
this.tradeDate = tradeStatistics.getDate();
try {
final Price tradePrice = getTradePrice();
Price tradePrice = getTradePrice();
if (CurrencyUtil.isCryptoCurrency(currency)) {
primaryMarketDirection = direction == OfferPayload.Direction.BUY ? OfferPayload.Direction.SELL : OfferPayload.Direction.BUY;
currencyPair = currency + "/" + Res.getBaseCurrencyCode();
primaryMarketTradePrice = tradePrice.getValue();
primaryMarketTradeAmount = getTradeVolume() != null ? getTradeVolume().getValue() : 0;
primaryMarketTradeAmount = getTradeVolume() != null ?
getTradeVolume().getValue() :
0;
primaryMarketTradeVolume = getTradeAmount().getValue();
} else {
primaryMarketDirection = direction;
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
primaryMarketTradePrice = (long) MathUtils.scaleUpByPowerOf10(tradePrice.getValue(), 4);
primaryMarketTradeAmount = getTradeAmount().getValue();
// we use precision 4 for fiat but on the markets api we use precision 8 so we scale up by 10000
primaryMarketTradeVolume = getTradeVolume() != null ?
(long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) : 0;
(long) MathUtils.scaleUpByPowerOf10(getTradeVolume().getValue(), 4) :
0;
}
} catch (Throwable t) {
log.error(t.getMessage());
@ -113,6 +91,10 @@ public final class TradeStatisticsForJson {
}
public Volume getTradeVolume() {
return getTradePrice().getVolumeByAmount(getTradeAmount());
try {
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 javax.inject.Named;
import javax.inject.Singleton;
import javafx.collections.FXCollections;
import javafx.collections.ObservableSet;
@ -40,94 +41,85 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j;
@Singleton
@Slf4j
public class TradeStatisticsManager {
private final JsonFileManager jsonFileManager;
private final P2PService p2PService;
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 ObservableSet<TradeStatistics2> observableTradeStatisticsSet = FXCollections.observableSet();
private final ObservableSet<TradeStatistics3> observableTradeStatisticsSet = FXCollections.observableSet();
private JsonFileManager jsonFileManager;
@Inject
public TradeStatisticsManager(P2PService p2PService,
PriceFeedService priceFeedService,
TradeStatistics2StorageService tradeStatistics2StorageService,
TradeStatistics3StorageService tradeStatistics3StorageService,
AppendOnlyDataStoreService appendOnlyDataStoreService,
TradeStatisticsConverter tradeStatisticsConverter,
@Named(Config.STORAGE_DIR) File storageDir,
@Named(Config.DUMP_STATISTICS) boolean dumpStatistics) {
this.p2PService = p2PService;
this.priceFeedService = priceFeedService;
this.tradeStatistics2StorageService = tradeStatistics2StorageService;
this.tradeStatistics3StorageService = tradeStatistics3StorageService;
this.tradeStatisticsConverter = tradeStatisticsConverter;
this.storageDir = storageDir;
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() {
p2PService.getP2PDataStorage().addAppendOnlyDataStoreListener(payload -> {
if (payload instanceof TradeStatistics2)
addToSet((TradeStatistics2) payload);
if (payload instanceof TradeStatistics3) {
TradeStatistics3 tradeStatistics = (TradeStatistics3) payload;
if (!tradeStatistics.isValid()) {
return;
}
observableTradeStatisticsSet.add(tradeStatistics);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
maybeDumpStatistics();
}
});
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)
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);
dump();
maybeDumpStatistics();
}
public ObservableSet<TradeStatistics2> getObservableTradeStatisticsSet() {
public ObservableSet<TradeStatistics3> 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()) {
return;
}
observableTradeStatisticsSet.add(tradeStatistics);
priceFeedService.applyLatestBisqMarketPrice(observableTradeStatisticsSet);
dump();
private void maybeDumpStatistics() {
if (!dumpStatistics) {
return;
}
}
private void dump() {
if (dumpStatistics) {
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()
.map(e -> new CurrencyTuple(e.getCode(), e.getName(), 8))
.collect(Collectors.toCollection(ArrayList::new));
@ -138,44 +130,14 @@ public class TradeStatisticsManager {
.collect(Collectors.toCollection(ArrayList::new));
cryptoCurrencyList.add(0, new CurrencyTuple(Res.getBaseCurrencyCode(), Res.getBaseCurrencyName(), 8));
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)
// TODO This is just a quick solution for storing to one file.
// 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)))
.collect(Collectors.toList());
TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()];
list.toArray(array);
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());
}
List<TradeStatisticsForJson> list = observableTradeStatisticsSet.stream()
.map(TradeStatisticsForJson::new)
.sorted((o1, o2) -> (Long.compare(o2.tradeDate, o1.tradeDate)))
.collect(Collectors.toList());
TradeStatisticsForJson[] array = new TradeStatisticsForJson[list.size()];
list.toArray(array);
jsonFileManager.writeToDisc(Utilities.objectToJson(array), "trade_statistics");
}
}

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;
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.GetTradeStatisticsReply;
@ -27,7 +27,7 @@ class GrpcGetTradeStatisticsService extends GetTradeStatisticsGrpc.GetTradeStati
StreamObserver<GetTradeStatisticsReply> responseObserver) {
var tradeStatistics = coreApi.getTradeStatistics().stream()
.map(TradeStatistics2::toProtoTradeStatistics2)
.map(TradeStatistics3::toProtoTradeStatistics3)
.collect(Collectors.toList());
var reply = GetTradeStatisticsReply.newBuilder().addAllTradeStatistics(tradeStatistics).build();

View file

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

View file

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

View file

@ -79,9 +79,6 @@ public class TextFieldWithCopyIcon extends AnchorPane {
AnchorPane.setRightAnchor(textField, 30.0);
AnchorPane.setLeftAnchor(textField, 0.0);
textField.focusTraversableProperty().set(focusTraversableProperty().get());
//TODO app wide focus
//focusedProperty().addListener((ov, oldValue, newValue) -> textField.requestFocus());
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.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, ?>>
extends ActivatableViewAndModel<VBox, T> {

View file

@ -30,7 +30,7 @@ import bisq.core.locale.Res;
import bisq.core.monetary.Altcoin;
import bisq.core.monetary.Price;
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.user.Preferences;
import bisq.core.util.FormattingUtils;
@ -121,7 +121,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
private BsqDashboardView(DaoFacade daoFacade,
public BsqDashboardView(DaoFacade daoFacade,
TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService,
Preferences preferences,
@ -173,7 +173,7 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
marketCapTextField = addTopLabelReadOnlyTextField(root, ++gridRow,
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;
}
@ -289,17 +289,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private void updateBsqPriceData() {
seriesBSQPrice.getData().clear();
Map<LocalDate, List<TradeStatistics2>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("BSQ"))
.sorted(Comparator.comparing(TradeStatistics2::getTradeDate))
.collect(Collectors.groupingBy(item -> new java.sql.Date(item.getTradeDate().getTime()).toLocalDate()
Map<LocalDate, List<TradeStatistics3>> bsqPriceByDate = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrency().equals("BSQ"))
.sorted(Comparator.comparing(TradeStatistics3::getDate))
.collect(Collectors.groupingBy(item -> new java.sql.Date(item.getDate()).toLocalDate()
.with(ADJUSTERS.get(DAY))));
List<XYChart.Data<Number, Number>> updatedBSQPrice = bsqPriceByDate.keySet().stream()
.map(e -> {
ZonedDateTime zonedDateTime = e.atStartOfDay(ZoneId.systemDefault());
return new XYChart.Data<Number, Number>(zonedDateTime.toInstant().getEpochSecond(), bsqPriceByDate.get(e).stream()
.map(TradeStatistics2::getTradePrice)
.map(TradeStatistics3::getTradePrice)
.mapToDouble(Price::getValue)
.average()
.orElse(Double.NaN)
@ -370,12 +370,12 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) {
Date pastXDays = getPastDate(days);
List<TradeStatistics2> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("BSQ"))
List<TradeStatistics3> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrency().equals("BSQ"))
.filter(e -> e.getTradeDate().after(pastXDays))
.collect(Collectors.toList());
List<TradeStatistics2> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrencyCode().equals("USD"))
List<TradeStatistics3> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrency().equals("USD"))
.filter(e -> e.getTradeDate().after(pastXDays))
.collect(Collectors.toList());
long average = isUSDField ? getUSDAverage(bsqTradePastXDays, usdTradePastXDays) :
@ -391,11 +391,11 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
return average;
}
private long getBTCAverage(List<TradeStatistics2> bsqList) {
private long getBTCAverage(List<TradeStatistics3> bsqList) {
long accumulatedVolume = 0;
long accumulatedAmount = 0;
for (TradeStatistics2 item : bsqList) {
for (TradeStatistics3 item : bsqList) {
accumulatedVolume += item.getTradeVolume().getValue();
accumulatedAmount += item.getTradeAmount().getValue(); // Amount of BTC traded
}
@ -406,17 +406,17 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
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
// Store each trade as amount of USD and amount of BSQ traded
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
for (TradeStatistics2 item : bsqList) {
for (TradeStatistics3 item : bsqList) {
// Find usdprice for trade item
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(),
Fiat.SMALLEST_UNIT_EXPONENT))
.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.ValidateOffer;
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.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
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.SellerProcessDelayedPayoutTxSignatureResponse;
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.SellerSendPayoutTxPublishedMessage;
import bisq.core.trade.protocol.tasks.seller.SellerSendsDepositTxAndDelayedPayoutTxMessage;
@ -145,7 +145,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class,
PublishTradeStatistics.class,
SellerPublishesTradeStatistics.class,
SellerProcessCounterCurrencyTransferStartedMessage.class,
ApplyFilter.class,
@ -179,7 +179,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class,
PublishTradeStatistics.class,
ApplyFilter.class,
MakerVerifyTakerFeePayment.class,
@ -216,7 +215,6 @@ public class DebugView extends InitializableView<GridPane, Void> {
BuyerProcessDepositTxAndDelayedPayoutTxMessage.class,
BuyerVerifiesFinalDelayedPayoutTx.class,
PublishTradeStatistics.class,
ApplyFilter.class,
TakerVerifyMakerFeePayment.class,
@ -248,7 +246,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerFinalizesDelayedPayoutTx.class,
SellerSendsDepositTxAndDelayedPayoutTxMessage.class,
SellerPublishesDepositTx.class,
PublishTradeStatistics.class,
SellerPublishesTradeStatistics.class,
SellerProcessCounterCurrencyTransferStartedMessage.class,
ApplyFilter.class,

View file

@ -35,7 +35,7 @@ import bisq.desktop.util.DisplayUtils;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
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.coin.CoinFormatter;
@ -82,7 +82,10 @@ public class MarketView extends ActivatableView<TabPane, Void> {
@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) {
this.viewLoader = viewLoader;
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 used a different referral ID then we would get 2 objects.
List<String> list = p2PService.getP2PDataStorage().getAppendOnlyDataStoreMap().values().stream()
.filter(e -> e instanceof TradeStatistics2)
.map(e -> (TradeStatistics2) e)
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap() != null)
.filter(tradeStatistics2 -> tradeStatistics2.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null)
.map(trade -> {
.filter(e -> e instanceof TradeStatistics3)
.map(e -> (TradeStatistics3) e)
.filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap() != null)
.filter(tradeStatistics3 -> tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID) != null)
.map(tradeStatistics3 -> {
StringBuilder sb = new StringBuilder();
sb.append("Trade ID: ").append(trade.getOfferId()).append("\n")
.append("Date: ").append(DisplayUtils.formatDateTime(trade.getTradeDate())).append("\n")
.append("Market: ").append(CurrencyUtil.getCurrencyPair(trade.getCurrencyCode())).append("\n")
.append("Price: ").append(FormattingUtils.formatPrice(trade.getTradePrice())).append("\n")
.append("Amount: ").append(formatter.formatCoin(trade.getTradeAmount())).append("\n")
.append("Volume: ").append(DisplayUtils.formatVolume(trade.getTradeVolume())).append("\n")
.append("Payment method: ").append(Res.get(trade.getOfferPaymentMethod())).append("\n")
.append("ReferralID: ").append(trade.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
sb.append("Date: ").append(DisplayUtils.formatDateTime(tradeStatistics3.getTradeDate())).append("\n")
.append("Market: ").append(CurrencyUtil.getCurrencyPair(tradeStatistics3.getCurrency())).append("\n")
.append("Price: ").append(FormattingUtils.formatPrice(tradeStatistics3.getTradePrice())).append("\n")
.append("Amount: ").append(formatter.formatCoin(tradeStatistics3.getTradeAmount())).append("\n")
.append("Volume: ").append(DisplayUtils.formatVolume(tradeStatistics3.getTradeVolume())).append("\n")
.append("Payment method: ").append(Res.get(tradeStatistics3.getPaymentMethod())).append("\n")
.append("ReferralID: ").append(tradeStatistics3.getExtraDataMap().get(OfferPayload.REFERRAL_ID));
return sb.toString();
})
.collect(Collectors.toList());

View file

@ -298,7 +298,7 @@ public class SpreadView extends ActivatableViewAndModel<GridPane, SpreadViewMode
public void updateItem(final SpreadItem item, boolean empty) {
super.updateItem(item, 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
if (item.priceSpread != null)
setText(item.percentage);

View file

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

View file

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

View file

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

View file

@ -23,14 +23,12 @@ import bisq.desktop.main.market.trades.charts.CandleData;
import bisq.core.locale.FiatCurrency;
import bisq.core.monetary.Price;
import bisq.core.offer.OfferPayload;
import bisq.core.payment.payload.PaymentMethod;
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.user.Preferences;
import bisq.common.crypto.KeyRing;
import bisq.common.crypto.KeyStorage;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
@ -49,9 +47,6 @@ import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
@ -63,9 +58,7 @@ public class TradesChartsViewModelTest {
TradesChartsViewModel model;
TradeStatisticsManager tradeStatisticsManager;
private static final Logger log = LoggerFactory.getLogger(TradesChartsViewModelTest.class);
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
private KeyRing keyRing;
private File dir;
OfferPayload offer = new OfferPayload(null,
0,
@ -106,6 +99,7 @@ public class TradesChartsViewModelTest {
null,
1
);
@Before
public void setup() throws IOException {
tradeStatisticsManager = mock(TradeStatisticsManager.class);
@ -116,7 +110,6 @@ public class TradesChartsViewModelTest {
dir.delete();
//noinspection ResultOfMethodCallIgnored
dir.mkdir();
keyRing = new KeyRing(new KeyStorage(dir));
}
@SuppressWarnings("ConstantConditions")
@ -134,13 +127,45 @@ public class TradesChartsViewModelTest {
long volume = Fiat.parseFiat("EUR", "2200").value;
boolean isBullish = true;
Set<TradeStatistics2> set = new HashSet<>();
Set<TradeStatistics3> set = new HashSet<>();
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 TradeStatistics2(offer, Price.parse("EUR", "500"), Coin.parseCoin("1"), new Date(now.getTime() + 100), null, null));
set.add(new TradeStatistics2(offer, Price.parse("EUR", "600"), Coin.parseCoin("1"), new Date(now.getTime() + 200), null, null));
set.add(new TradeStatistics2(offer, Price.parse("EUR", "580"), Coin.parseCoin("1"), new Date(now.getTime() + 300), null, null));
set.add(new TradeStatistics3(offer.getCurrencyCode(),
Price.parse("EUR", "520").getValue(),
Coin.parseCoin("1").getValue(),
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);
assertEquals(open, candleData.open);
@ -183,7 +208,6 @@ public class TradesChartsViewModelTest {
ArrayList<Trade> trades = new ArrayList<>();
// Set predetermined time to use as "now" during test
Date test_time = dateFormat.parse("2018-01-01T00:00:05"); // Monday
/* new MockUp<System>() {
@Mock
long currentTimeMillis() {
@ -194,11 +218,19 @@ public class TradesChartsViewModelTest {
// 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("2018-01-01T00:00:02", "1", "110", "EUR"));
Set<TradeStatistics2> set = new HashSet<>();
Set<TradeStatistics3> set = new HashSet<>();
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
for (TradesChartsViewModel.TickUnit tick : TradesChartsViewModel.TickUnit.values()) {
@ -209,7 +241,7 @@ public class TradesChartsViewModelTest {
// Trigger chart update
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.volumeItems.size());
}

View file

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

View file

@ -20,30 +20,15 @@ package bisq.monitor.metric;
import bisq.monitor.Metric;
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.URLConnection;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
@ -110,7 +95,6 @@ public class MarketStats extends Metric {
} catch (IllegalStateException ignore) {
// no match found
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

View file

@ -23,7 +23,7 @@ import bisq.monitor.Reporter;
import bisq.core.account.witness.AccountAgeWitnessStore;
import bisq.core.offer.OfferPayload;
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.network.Connection;
@ -163,8 +163,8 @@ public class P2PMarketStats extends P2PSeedNodeSnapshotBase {
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
try {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
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.GetStateHashesResponse;
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.network.Connection;
@ -137,8 +137,8 @@ public class P2PSeedNodeSnapshot extends P2PSeedNodeSnapshotBase {
String networkPostfix = "_" + BaseCurrencyNetwork.values()[Version.getBaseCurrencyNetwork()].toString();
try {
PersistenceManager<PersistableEnvelope> persistenceManager = new PersistenceManager<>(dir, new CorePersistenceProtoResolver(null, null), null);
TradeStatistics2Store tradeStatistics2Store = (TradeStatistics2Store) persistenceManager.getPersisted(TradeStatistics2Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics2Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
TradeStatistics3Store tradeStatistics3Store = (TradeStatistics3Store) persistenceManager.getPersisted(TradeStatistics3Store.class.getSimpleName() + networkPostfix);
hashes.addAll(tradeStatistics3Store.getMap().keySet().stream().map(byteArray -> byteArray.bytes).collect(Collectors.toList()));
AccountAgeWitnessStore accountAgeWitnessStore = (AccountAgeWitnessStore) persistenceManager.getPersisted(AccountAgeWitnessStore.class.getSimpleName() + networkPostfix);
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;
// - update queried height
if(type.contains("DaoState"))
if (type.contains("DaoState"))
daostateheight = oldest - 20;
else if(type.contains("Proposal"))
else if (type.contains("Proposal"))
proposalheight = oldest - 20;
else
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());
hitcount.clear();
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

View file

@ -54,7 +54,7 @@ import static com.google.common.base.Preconditions.checkNotNull;
/**
* Fetches fee and price data from the configured price nodes.
* Based on the work of HarryMcFinned.
*
*
* @author Florian Reimair
* @author HarryMcFinned
*
@ -159,7 +159,6 @@ public class PriceNodeStats extends Metric {
}
}
} catch (TorCtlException | IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}

View file

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

View file

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

View file

@ -920,6 +920,13 @@ public class P2PService implements SetupListener, MessageListener, ConnectionLis
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
public class MailboxItem {

View file

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

Binary file not shown.

Binary file not shown.

View file

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

View file

@ -510,9 +510,7 @@ message StoragePayload {
Mediator mediator = 3;
Filter filter = 4;
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
// versions and convert it to TradeStatistics2 objects.
TradeStatistics trade_statistics = 5 [deprecated = true];
// TradeStatistics trade_statistics = 5 [deprecated = true]; Removed in v.1.4.0
MailboxStoragePayload mailbox_storage_payload = 6;
OfferPayload offer_payload = 7;
@ -524,10 +522,11 @@ message StoragePayload {
message PersistableNetworkPayload {
oneof message {
AccountAgeWitness account_age_witness = 1;
TradeStatistics2 trade_statistics2 = 2;
TradeStatistics2 trade_statistics2 = 2 [deprecated = true];
ProposalPayload proposal_payload = 3;
BlindVotePayload blind_vote_payload = 4;
SignedWitness signed_witness = 5;
TradeStatistics3 trade_statistics3 = 6;
}
}
@ -650,44 +649,36 @@ message Filter {
bool disable_auto_conf = 24;
}
// not used anymore from v0.6 on. But leave it for receiving TradeStatistics objects from older
// versions and convert it to TradeStatistics2 objects.
message TradeStatistics {
string base_currency = 1;
string counter_currency = 2;
OfferPayload.Direction direction = 3;
int64 trade_price = 4;
int64 trade_amount = 5;
int64 trade_date = 6;
string payment_method_id = 7;
int64 offer_date = 8;
bool offer_use_market_based_price = 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 signature_pub_key_bytes = 15;
map<string, string> extra_data = 16;
// Deprecated
message TradeStatistics2 {
string base_currency = 1 [deprecated = true];
string counter_currency = 2 [deprecated = true];
OfferPayload.Direction direction = 3 [deprecated = true];
int64 trade_price = 4 [deprecated = true];
int64 trade_amount = 5 [deprecated = true];
int64 trade_date = 6 [deprecated = true];
string payment_method_id = 7 [deprecated = true];
int64 offer_date = 8 [deprecated = true];
bool offer_use_market_based_price = 9 [deprecated = true];
double offer_market_price_margin = 10 [deprecated = true];
int64 offer_amount = 11 [deprecated = true];
int64 offer_min_amount = 12 [deprecated = true];
string offer_id = 13 [deprecated = true];
string deposit_tx_id = 14 [deprecated = true];
bytes hash = 15 [deprecated = true];
map<string, string> extra_data = 16 [deprecated = true];
}
message TradeStatistics2 {
string base_currency = 1;
string counter_currency = 2;
OfferPayload.Direction direction = 3;
int64 trade_price = 4;
int64 trade_amount = 5;
int64 trade_date = 6;
string payment_method_id = 7;
int64 offer_date = 8;
bool offer_use_market_based_price = 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 TradeStatistics3 {
string currency = 1;
int64 price = 2;
int64 amount = 3;
string payment_method = 4;
int64 date = 5;
string mediator = 6;
string refund_agent = 7;
bytes hash = 8;
map<string, string> extra_data = 9;
}
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
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
@ -1171,6 +1162,7 @@ message PersistableEnvelope {
SignedWitnessStore signed_witness_store = 28;
MediationDisputeList mediation_dispute_list = 29;
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
// Deprecated
message TradeStatistics2Store {
repeated TradeStatistics2 items = 1;
repeated TradeStatistics2 items = 1 [deprecated = true];
}
message TradeStatistics3Store {
repeated TradeStatistics3 items = 1;
}
message PeerList {