Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve witness handling #3342

Merged
merged 6 commits into from
Sep 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.core.account.sign.SignedWitness;
import bisq.core.account.sign.SignedWitnessService;
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.offer.OfferRestrictions;
Expand Down Expand Up @@ -77,6 +78,7 @@
public class AccountAgeWitnessService {
private static final Date RELEASE = Utilities.getUTCDate(2017, GregorianCalendar.NOVEMBER, 11);
public static final Date FULL_ACTIVATION = Utilities.getUTCDate(2018, GregorianCalendar.FEBRUARY, 15);
// TODO: Change to March, 1, 2019 before release
private static final long SAFE_ACCOUNT_AGE_DATE = Utilities.getUTCDate(2019, GregorianCalendar.SEPTEMBER, 1).getTime();

public enum AccountAge {
Expand All @@ -86,6 +88,25 @@ public enum AccountAge {
TWO_MONTHS_OR_MORE
}

public enum SignState {
UNSIGNED(Res.get("offerbook.timeSinceSigning.notSigned")),
ARBITRATOR(Res.get("offerbook.timeSinceSigning.info.arbitrator")),
PEER_INITIAL(Res.get("offerbook.timeSinceSigning.info.peer")),
PEER_LIMIT_LIFTED(Res.get("offerbook.timeSinceSigning.info.peerLimitLifted")),
PEER_SIGNER(Res.get("offerbook.timeSinceSigning.info.signer"));

private String presentation;

SignState(String presentation) {
this.presentation = presentation;
}

public String getPresentation() {
return presentation;
}

}

private final KeyRing keyRing;
private final P2PService p2PService;
private final User user;
Expand Down Expand Up @@ -238,6 +259,12 @@ public long getAccountAge(PaymentAccountPayload paymentAccountPayload, PubKeyRin
.orElse(-1L);
}

public long getAccountAge(Offer offer) {
return findWitness(offer)
.map(accountAgeWitness -> getAccountAge(accountAgeWitness, new Date()))
.orElse(-1L);
}

///////////////////////////////////////////////////////////////////////////////////////////
// Signed age
///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -293,12 +320,18 @@ private long getTradeLimit(Coin maxTradeLimit,
String currencyCode,
AccountAgeWitness accountAgeWitness,
AccountAge accountAgeCategory,
OfferPayload.Direction direction) {
OfferPayload.Direction direction,
PaymentMethod paymentMethod) {
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
double factor;

boolean isRisky = PaymentMethod.hasChargebackRisk(paymentMethod, currencyCode);
if (!isRisky) {
// Get age of witness rather than time since signing for non risky payment methods
accountAgeCategory = getAccountAgeCategory(getAccountAge(accountAgeWitness, new Date()));
}
long limit = OfferRestrictions.TOLERATED_SMALL_TRADE_AMOUNT.value;
if (direction == OfferPayload.Direction.BUY) {
if (direction == OfferPayload.Direction.BUY && isRisky) {
// Used only for bying of BTC with risky payment methods
switch (accountAgeCategory) {
case TWO_MONTHS_OR_MORE:
factor = 1;
Expand All @@ -312,6 +345,7 @@ private long getTradeLimit(Coin maxTradeLimit,
factor = 0;
}
} else {
// Used by non risky payment methods and for selling BTC with risky methods
switch (accountAgeCategory) {
case TWO_MONTHS_OR_MORE:
factor = 1;
Expand Down Expand Up @@ -347,8 +381,7 @@ private long getTradeLimit(Coin maxTradeLimit,
///////////////////////////////////////////////////////////////////////////////////////////

private boolean isImmature(AccountAgeWitness accountAgeWitness) {
return accountAgeWitness.getDate() > SAFE_ACCOUNT_AGE_DATE &&
getWitnessSignAge(accountAgeWitness, new Date()) < 0;
return accountAgeWitness.getDate() > SAFE_ACCOUNT_AGE_DATE;
}

public boolean isMyAccountAgeImmature(PaymentAccount myPaymentAccount) {
Expand Down Expand Up @@ -393,7 +426,8 @@ public long getMyTradeLimit(PaymentAccount paymentAccount, String currencyCode,
currencyCode,
accountAgeWitness,
accountAgeCategory,
direction);
direction,
paymentAccount.getPaymentMethod());
}

///////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -514,7 +548,7 @@ private boolean verifyPeersTradeLimit(Offer offer,
OfferPayload.Direction direction = offer.isMyOffer(keyRing) ?
offer.getMirroredDirection() : offer.getDirection();
peersCurrentTradeLimit = getTradeLimit(defaultMaxTradeLimit, currencyCode, peersWitness,
accountAgeCategory, direction);
accountAgeCategory, direction, offer.getPaymentMethod());
}
// Makers current trade limit cannot be smaller than that in the offer
boolean result = tradeAmount.value <= peersCurrentTradeLimit;
Expand Down Expand Up @@ -651,4 +685,29 @@ public boolean accountIsSigner(AccountAgeWitness accountAgeWitness) {
}
return getWitnessSignAge(accountAgeWitness, new Date()) > SignedWitnessService.SIGNER_AGE;
}

public SignState getSignState(Offer offer) {
return findWitness(offer)
.map(this::getSignState)
.orElse(SignState.UNSIGNED);
}

public SignState getSignState(AccountAgeWitness accountAgeWitness) {
if (signedWitnessService.isSignedByArbitrator(accountAgeWitness)) {
return SignState.ARBITRATOR;
} else {
final long accountSignAge = getWitnessSignAge(accountAgeWitness, new Date());
switch (getAccountAgeCategory(accountSignAge)) {
case TWO_MONTHS_OR_MORE:
return SignState.PEER_SIGNER;
case ONE_TO_TWO_MONTHS:
return SignState.PEER_LIMIT_LIFTED;
case LESS_ONE_MONTH:
return SignState.PEER_INITIAL;
case UNVERIFIED:
default:
return SignState.UNSIGNED;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public static boolean isAmountValidForOffer(Offer offer,
AccountAgeWitnessService accountAgeWitnessService) {
boolean hasChargebackRisk = PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
boolean hasValidAccountAgeWitness = accountAgeWitnessService.getMyTradeLimit(paymentAccount,
offer.getCurrencyCode(), offer.getMirroredDirection()) > offer.getAmount().value;
offer.getCurrencyCode(), offer.getMirroredDirection()) >= offer.getAmount().value;
return !hasChargebackRisk || hasValidAccountAgeWitness;
}

Expand Down
8 changes: 8 additions & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -331,10 +331,18 @@ offerbook.availableOffers=Available offers
offerbook.filterByCurrency=Filter by currency
offerbook.filterByPaymentMethod=Filter by payment method
offerbook.timeSinceSigning=Time since signing
offerbook.timeSinceSigning.info=This account was verified and {0}
offerbook.timeSinceSigning.info.arbitrator=signed by an arbitrator and can sign peers accounts
offerbook.timeSinceSigning.info.peer=signed by a peer, waiting for limits to be lifted
offerbook.timeSinceSigning.info.peerLimitLifted=signed by a peer and limits were lifted
offerbook.timeSinceSigning.info.signer=signed by peer and can sign peers accounts
offerbook.timeSinceSigning.daysSinceSigning={0} days
offerbook.timeSinceSigning.help=By trading with a payment account that was verified by an arbitrator or a peer, your account gets signed as well.\n\
30 days later the initial limit of 0.01 BTC gets lifted and after 90 days your account can sign other peers as well.
offerbook.timeSinceSigning.notSigned=Not signed yet
offerbook.timeSinceSigning.notSigned.noNeed=Unsigned
shared.notSigned=This account hasn't been signed yet.
shared.notSigned.noNeed=This account type doesn't use signing

offerbook.nrOffers=No. of offers: {0}
offerbook.volume={0} (min - max)
Expand Down
35 changes: 18 additions & 17 deletions desktop/src/main/java/bisq/desktop/components/PeerInfoIcon.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import bisq.core.locale.CurrencyUtil;
import bisq.core.locale.Res;
import bisq.core.offer.Offer;
import bisq.core.payment.payload.PaymentMethod;
import bisq.core.trade.Trade;
import bisq.core.user.Preferences;
import bisq.core.util.BSFormatter;
Expand All @@ -33,6 +34,8 @@

import com.google.common.base.Charsets;

import org.apache.commons.lang3.StringUtils;

import javafx.scene.Group;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
Expand Down Expand Up @@ -152,6 +155,7 @@ private PeerInfoIcon(NodeAddress nodeAddress,
// outer circle
Color ringColor;
if (isFiatCurrency) {

switch (accountAgeWitnessService.getPeersAccountAgeCategory(peersAccountAge)) {
case TWO_MONTHS_OR_MORE:
ringColor = Color.rgb(0, 225, 0); // > 2 months green
Expand Down Expand Up @@ -243,16 +247,11 @@ private PeerInfoIcon(NodeAddress nodeAddress,

getChildren().addAll(outerBackground, innerBackground, avatarImageView, tagPane, numTradesPane);

//TODO sqrrm: We need these states in here:
// - signed by arbitrator
// - signed by peer
// - signed by peer and limit lifted
// - signed by peer and able to sign
// - not signing necessary for this payment account
// - signing required and not signed
// Additionally we need to have some enum or so how the account signing took place.
// e.g. if in the future we'll also offer the "pay with two different accounts"-signing
String accountSigningState = Res.get("shared.notSigned");
boolean needsSigning = PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode());
String accountSigningState = Res.get("shared.notSigned.noNeed");
if (needsSigning) {
accountSigningState = StringUtils.capitalize(accountAgeWitnessService.getSignState(offer).getPresentation());
}

addMouseListener(numTrades, privateNotificationManager, offer, preferences, formatter, useDevPrivilegeKeys, isFiatCurrency, peersAccountAge, accountSigningState);
}
Expand All @@ -264,13 +263,12 @@ private long getPeersAccountAge(@Nullable Trade trade, @Nullable Offer offer) {
// unexpected
return -1;
}

return accountAgeWitnessService.getWitnessSignAge(trade, new Date());
} else {
checkNotNull(offer, "Offer must not be null if trade is null.");

}
checkNotNull(offer, "Offer must not be null if trade is null.");
if (PaymentMethod.hasChargebackRisk(offer.getPaymentMethod(), offer.getCurrencyCode())) {
return accountAgeWitnessService.getWitnessSignAge(offer, new Date());
}
return accountAgeWitnessService.getAccountAge(offer);
}

protected void addMouseListener(int numTrades,
Expand All @@ -279,7 +277,9 @@ protected void addMouseListener(int numTrades,
Preferences preferences,
BSFormatter formatter,
boolean useDevPrivilegeKeys,
boolean isFiatCurrency, long makersAccountAge, String accountSigningState) {
boolean isFiatCurrency,
long makersAccountAge,
String accountSigningState) {
final String accountAgeTagEditor = isFiatCurrency ?
makersAccountAge > -1 ?
DisplayUtils.formatAccountAge(makersAccountAge) :
Expand All @@ -289,7 +289,8 @@ protected void addMouseListener(int numTrades,
setOnMouseClicked(e -> new PeerInfoWithTagEditor(privateNotificationManager, offer, preferences, useDevPrivilegeKeys)
.fullAddress(fullAddress)
.numTrades(numTrades)
.accountAge(accountAgeTagEditor).accountSigningState(accountSigningState)
.accountAge(accountAgeTagEditor)
.accountSigningState(accountSigningState)
.position(localToScene(new Point2D(0, 0)))
.onSave(newTag -> {
preferences.setTagForPeer(fullAddress, newTag);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@
import javafx.util.StringConverter;

import java.util.Comparator;
import java.util.Date;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -1069,25 +1071,25 @@ public void updateItem(final OfferBookListItem item, boolean empty) {
String timeSinceSigning;

if (accountAgeWitnessService.hasSignedWitness(item.getOffer())) {
//TODO sqrrm: We need four states in here:
// - signed by arbitrator
// - signed by peer
// - signed by peer and limit lifted
// - signed by peer and able to sign
// Additionally we need to have some enum or so how the account signing took place.
// e.g. if in the future we'll also offer the "pay with two different accounts"-signing
icon = MaterialDesignIcon.APPROVAL;
info = "This account was verified and signed by an arbitrator or peer.";
//TODO sqrrm: add time since signing
timeSinceSigning = "3 days";
info = Res.get("offerbook.timeSinceSigning.info",
accountAgeWitnessService.getSignState(item.getOffer()).getPresentation());
long daysSinceSigning = TimeUnit.MILLISECONDS.toDays(
accountAgeWitnessService.getWitnessSignAge(item.getOffer(), new Date()));
timeSinceSigning = Res.get("offerbook.timeSinceSigning.daysSinceSigning",
daysSinceSigning);
} else {

//TODO sqrrm: Here we need two states:
// - not signing necessary for this payment account
// - signing required and not signed
icon = MaterialDesignIcon.ALERT_CIRCLE_OUTLINE;
info = Res.get("shared.notSigned");
timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned");
boolean needsSigning = PaymentMethod.hasChargebackRisk(
item.getOffer().getPaymentMethod(), item.getOffer().getCurrencyCode());
if (needsSigning) {
icon = MaterialDesignIcon.ALERT_CIRCLE_OUTLINE;
info = Res.get("shared.notSigned");
timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned");
} else {
icon = MaterialDesignIcon.INFORMATION_OUTLINE;
info = Res.get("shared.notSigned.noNeed");
timeSinceSigning = Res.get("offerbook.timeSinceSigning.notSigned.noNeed");
}
}

InfoAutoTooltipLabel label = new InfoAutoTooltipLabel(timeSinceSigning, icon, ContentDisplay.RIGHT, info);
Expand Down