diff --git a/common/src/main/java/bisq/common/util/Utilities.java b/common/src/main/java/bisq/common/util/Utilities.java index f958bdd9b9..bfa0d7df54 100644 --- a/common/src/main/java/bisq/common/util/Utilities.java +++ b/common/src/main/java/bisq/common/util/Utilities.java @@ -334,6 +334,10 @@ public class Utilities { return new KeyCodeCombination(keyCode, KeyCombination.ALT_DOWN).match(keyEvent); } + public static boolean isCtrlShiftPressed(KeyCode keyCode, KeyEvent keyEvent) { + return new KeyCodeCombination(keyCode, KeyCombination.CONTROL_DOWN, KeyCombination.SHIFT_DOWN).match(keyEvent); + } + public static byte[] concatenateByteArrays(byte[] array1, byte[] array2) { return ArrayUtils.addAll(array1, array2); } diff --git a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java index 43a11a7f12..79beae2e58 100644 --- a/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java +++ b/core/src/main/java/bisq/core/account/sign/SignedWitnessService.java @@ -334,10 +334,11 @@ public class SignedWitnessService { .collect(Collectors.toSet()); } - public Set getOrphanSignedWitnessSet() { + public Set getRootSignedWitnessSet(boolean includeSignedByArbitrator) { return signedWitnessMap.values().stream() .filter(witness -> getSignedWitnessSetByOwnerPubKey(witness.getSignerPubKey(), new Stack<>()).isEmpty()) - .filter(witness -> witness.getVerificationMethod() != SignedWitness.VerificationMethod.ARBITRATOR) + .filter(witness -> includeSignedByArbitrator || + witness.getVerificationMethod() != SignedWitness.VerificationMethod.ARBITRATOR) .collect(Collectors.toSet()); } diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java index 10ef74ab60..46e6e821a8 100644 --- a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessService.java @@ -74,11 +74,8 @@ import java.util.Objects; import java.util.Optional; import java.util.Random; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Stack; import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Predicate; -import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -137,6 +134,8 @@ public class AccountAgeWitnessService { private final SignedWitnessService signedWitnessService; private final ChargeBackRisk chargeBackRisk; private final FilterManager filterManager; + @Getter + private final AccountAgeWitnessUtils accountAgeWitnessUtils; @Getter private final Map accountAgeWitnessMap = new HashMap<>(); @@ -163,6 +162,11 @@ public class AccountAgeWitnessService { this.chargeBackRisk = chargeBackRisk; this.filterManager = filterManager; + accountAgeWitnessUtils = new AccountAgeWitnessUtils( + this, + signedWitnessService, + keyRing); + // We need to add that early (before onAllServicesInitialized) as it will be used at startup. appendOnlyDataStoreService.addService(accountAgeWitnessStorageService); } @@ -231,7 +235,7 @@ public class AccountAgeWitnessService { .orElse(null); } - private byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) { + byte[] getAccountInputDataWithSalt(PaymentAccountPayload paymentAccountPayload) { return Utilities.concatenateByteArrays(paymentAccountPayload.getAgeWitnessInputData(), paymentAccountPayload.getSalt()); } @@ -243,7 +247,7 @@ public class AccountAgeWitnessService { return new AccountAgeWitness(hash, new Date().getTime()); } - private Optional findWitness(PaymentAccountPayload paymentAccountPayload, + Optional findWitness(PaymentAccountPayload paymentAccountPayload, PubKeyRing pubKeyRing) { byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload); byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt, @@ -646,7 +650,7 @@ public class AccountAgeWitnessService { ECKey key, byte[] tradersPubKey, long time) { - return signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, tradersPubKey,time); + return signedWitnessService.signAccountAgeWitness(accountAgeWitness, key, tradersPubKey, time); } public void traderSignPeersAccountAgeWitness(Trade trade) { @@ -795,92 +799,9 @@ public class AccountAgeWitnessService { } public Set getOrphanSignedWitnesses() { - var orphans = signedWitnessService.getOrphanSignedWitnessSet().stream() + return signedWitnessService.getRootSignedWitnessSet(false).stream() .map(signedWitness -> getWitnessByHash(signedWitness.getAccountAgeWitnessHash()).orElse(null)) .filter(Objects::nonNull) .collect(Collectors.toSet()); - var orphanSigners = signedWitnessService.getOrphanSignedWitnessSet().stream() - .filter(Utilities.distinctByKey(w -> Utilities.bytesAsHexString(w.getSignerPubKey()))) - .collect(Collectors.toSet()); - var orphanAEW = signedWitnessService.getOrphanSignedWitnessSet(); - log.debug("Orphaned signed account age witnesses:"); - orphanSigners.forEach(w -> { - log.debug("{}: {}", w.getVerificationMethod().toString(), - Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getSignerPubKey()))); - logChild(w, " ", new HashSet<>()); - }); - return orphans; - } - - private void logChild(SignedWitness sigWit, String initString, Set excluded) { - var allSig = signedWitnessService.getSignedWitnessMap(); - log.debug("{}{}", initString, Utilities.bytesAsHexString(sigWit.getAccountAgeWitnessHash())); - allSig.values().forEach(w -> { - if (Arrays.equals(w.getSignerPubKey(), sigWit.getWitnessOwnerPubKey()) && !excluded.contains(w)) { - excluded.add(sigWit); - logChild(w, initString + " ", excluded); - } - }); - } - - /////////////////////////////////////////////////////////////////////////////////////////// - // Debug logs - /////////////////////////////////////////////////////////////////////////////////////////// - - private String getWitnessDebugLog(PaymentAccountPayload paymentAccountPayload, - PubKeyRing pubKeyRing) { - Optional accountAgeWitness = findWitness(paymentAccountPayload, pubKeyRing); - if (!accountAgeWitness.isPresent()) { - byte[] accountInputDataWithSalt = getAccountInputDataWithSalt(paymentAccountPayload); - byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt, - pubKeyRing.getSignaturePubKeyBytes())); - return "No accountAgeWitness found for paymentAccountPayload with hash " + Utilities.bytesAsHexString(hash); - } - - SignState signState = getSignState(accountAgeWitness.get()); - return signState.name() + " " + signState.getPresentation() + - "\n" + accountAgeWitness.toString(); - } - - public void witnessDebugLog(Trade trade, @Nullable AccountAgeWitness myWitness) { - // Log to find why accounts sometimes don't get signed as expected - // TODO: Demote to debug or remove once account signing is working ok - checkNotNull(trade.getContract()); - checkNotNull(trade.getContract().getBuyerPaymentAccountPayload()); - boolean checkingSignTrade = true; - boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing()); - AccountAgeWitness witness = myWitness; - if (witness == null) { - witness = isBuyer ? - getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) : - getMyWitness(trade.getContract().getSellerPaymentAccountPayload()); - checkingSignTrade = false; - } - boolean isSignWitnessTrade = accountIsSigner(witness) && - !peerHasSignedWitness(trade) && - tradeAmountIsSufficient(trade.getTradeAmount()); - log.info("AccountSigning: " + - "\ntradeId: {}" + - "\nis buyer: {}" + - "\nbuyer account age witness info: {}" + - "\nseller account age witness info: {}" + - "\nchecking for sign trade: {}" + - "\nis myWitness signer: {}" + - "\npeer has signed witness: {}" + - "\ntrade amount: {}" + - "\ntrade amount is sufficient: {}" + - "\nisSignWitnessTrade: {}", - trade.getId(), - isBuyer, - getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(), - trade.getContract().getBuyerPubKeyRing()), - getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(), - trade.getContract().getSellerPubKeyRing()), - checkingSignTrade, // Following cases added to use same logic as in seller signing check - accountIsSigner(witness), - peerHasSignedWitness(trade), - trade.getTradeAmount(), - tradeAmountIsSufficient(trade.getTradeAmount()), - isSignWitnessTrade); } } diff --git a/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java new file mode 100644 index 0000000000..5fc14b579e --- /dev/null +++ b/core/src/main/java/bisq/core/account/witness/AccountAgeWitnessUtils.java @@ -0,0 +1,158 @@ +/* + * 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 . + */ + +package bisq.core.account.witness; + +import bisq.core.account.sign.SignedWitness; +import bisq.core.account.sign.SignedWitnessService; +import bisq.core.payment.payload.PaymentAccountPayload; +import bisq.core.trade.Trade; + +import bisq.network.p2p.storage.P2PDataStorage; + +import bisq.common.crypto.Hash; +import bisq.common.crypto.KeyRing; +import bisq.common.crypto.PubKeyRing; +import bisq.common.util.Utilities; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Stack; + +import lombok.extern.slf4j.Slf4j; + +import javax.annotation.Nullable; + +import static com.google.common.base.Preconditions.checkNotNull; + +@Slf4j +public class AccountAgeWitnessUtils { + private final AccountAgeWitnessService accountAgeWitnessService; + private final SignedWitnessService signedWitnessService; + private final KeyRing keyRing; + + AccountAgeWitnessUtils(AccountAgeWitnessService accountAgeWitnessService, + SignedWitnessService signedWitnessService, + KeyRing keyRing) { + this.accountAgeWitnessService = accountAgeWitnessService; + this.signedWitnessService = signedWitnessService; + this.keyRing = keyRing; + } + + // Log tree of signed witnesses + public void logSignedWitnesses() { + var orphanSigners = signedWitnessService.getRootSignedWitnessSet(false); + log.info("Orphaned signed account age witnesses:"); + orphanSigners.forEach(w -> { + log.warn("{}: {}", w.getVerificationMethod().toString(), + Utilities.bytesAsHexString(Hash.getRipemd160hash(w.getSignerPubKey()))); + logChild(w, " ", new Stack<>()); + }); + } + + private void logChild(SignedWitness sigWit, String initString, Stack excluded) { + var allSig = signedWitnessService.getSignedWitnessMap(); + log.warn("{}{}", initString, Utilities.bytesAsHexString(sigWit.getAccountAgeWitnessHash())); + allSig.values().forEach(w -> { + if (!excluded.contains(new P2PDataStorage.ByteArray(w.getWitnessOwnerPubKey())) && + Arrays.equals(w.getSignerPubKey(), sigWit.getWitnessOwnerPubKey())) { + excluded.push(new P2PDataStorage.ByteArray(w.getWitnessOwnerPubKey())); + logChild(w, initString + " ", excluded); + excluded.pop(); + } + }); + } + + // Log signers per + public void logSigners() { + log.info("signers per AEW"); + var allSig = signedWitnessService.getSignedWitnessMap(); + allSig.values().forEach(w -> { + log.info("AEW {}", Utilities.bytesAsHexString(w.getAccountAgeWitnessHash())); + allSig.values().forEach(ww -> { + if (Arrays.equals(w.getSignerPubKey(), ww.getWitnessOwnerPubKey())) { + log.info(" {}", Utilities.bytesAsHexString(ww.getAccountAgeWitnessHash())); + } + }); + } + ); + } + + /////////////////////////////////////////////////////////////////////////////////////////// + // Debug logs + /////////////////////////////////////////////////////////////////////////////////////////// + + private String getWitnessDebugLog(PaymentAccountPayload paymentAccountPayload, + PubKeyRing pubKeyRing) { + Optional accountAgeWitness = + accountAgeWitnessService.findWitness(paymentAccountPayload, pubKeyRing); + if (!accountAgeWitness.isPresent()) { + byte[] accountInputDataWithSalt = + accountAgeWitnessService.getAccountInputDataWithSalt(paymentAccountPayload); + byte[] hash = Hash.getSha256Ripemd160hash(Utilities.concatenateByteArrays(accountInputDataWithSalt, + pubKeyRing.getSignaturePubKeyBytes())); + return "No accountAgeWitness found for paymentAccountPayload with hash " + Utilities.bytesAsHexString(hash); + } + + AccountAgeWitnessService.SignState signState = + accountAgeWitnessService.getSignState(accountAgeWitness.get()); + return signState.name() + " " + signState.getPresentation() + + "\n" + accountAgeWitness.toString(); + } + + public void witnessDebugLog(Trade trade, @Nullable AccountAgeWitness myWitness) { + // Log to find why accounts sometimes don't get signed as expected + // TODO: Demote to debug or remove once account signing is working ok + checkNotNull(trade.getContract()); + checkNotNull(trade.getContract().getBuyerPaymentAccountPayload()); + boolean checkingSignTrade = true; + boolean isBuyer = trade.getContract().isMyRoleBuyer(keyRing.getPubKeyRing()); + AccountAgeWitness witness = myWitness; + if (witness == null) { + witness = isBuyer ? + accountAgeWitnessService.getMyWitness(trade.getContract().getBuyerPaymentAccountPayload()) : + accountAgeWitnessService.getMyWitness(trade.getContract().getSellerPaymentAccountPayload()); + checkingSignTrade = false; + } + boolean isSignWitnessTrade = accountAgeWitnessService.accountIsSigner(witness) && + !accountAgeWitnessService.peerHasSignedWitness(trade) && + accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()); + log.info("AccountSigning: " + + "\ntradeId: {}" + + "\nis buyer: {}" + + "\nbuyer account age witness info: {}" + + "\nseller account age witness info: {}" + + "\nchecking for sign trade: {}" + + "\nis myWitness signer: {}" + + "\npeer has signed witness: {}" + + "\ntrade amount: {}" + + "\ntrade amount is sufficient: {}" + + "\nisSignWitnessTrade: {}", + trade.getId(), + isBuyer, + getWitnessDebugLog(trade.getContract().getBuyerPaymentAccountPayload(), + trade.getContract().getBuyerPubKeyRing()), + getWitnessDebugLog(trade.getContract().getSellerPaymentAccountPayload(), + trade.getContract().getSellerPubKeyRing()), + checkingSignTrade, // Following cases added to use same logic as in seller signing check + accountAgeWitnessService.accountIsSigner(witness), + accountAgeWitnessService.peerHasSignedWitness(trade), + trade.getTradeAmount(), + accountAgeWitnessService.tradeAmountIsSufficient(trade.getTradeAmount()), + isSignWitnessTrade); + } +} diff --git a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java index 6f575be684..27317d7569 100644 --- a/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java +++ b/core/src/main/java/bisq/core/trade/protocol/ProcessModel.java @@ -358,6 +358,6 @@ public class ProcessModel implements Model, PersistablePayload { } void logTrade(Trade trade) { - accountAgeWitnessService.witnessDebugLog(trade, null); + accountAgeWitnessService.getAccountAgeWitnessUtils().witnessDebugLog(trade, null); } } diff --git a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java index a0cdd3798b..c8e1cc6b59 100644 --- a/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java +++ b/core/src/test/java/bisq/core/account/witness/AccountAgeWitnessServiceTest.java @@ -324,11 +324,11 @@ public class AccountAgeWitnessServiceTest { assertFalse(signedWitnessService.isSignedAccountAgeWitness(aew2)); // Sign dummy AccountAgeWitness using signer key from SW_1 - assertEquals(signedWitnessService.getOrphanSignedWitnessSet().size(), 1); + assertEquals(signedWitnessService.getRootSignedWitnessSet().size(), 1); // TODO: move this to accountagewitnessservice @SuppressWarnings("OptionalGetWithoutIsPresent") - var orphanedSignedWitness = signedWitnessService.getOrphanSignedWitnessSet().stream().findAny().get(); + var orphanedSignedWitness = signedWitnessService.getRootSignedWitnessSet().stream().findAny().get(); var dummyAccountAgeWitnessHash = Hash.getRipemd160hash(orphanedSignedWitness.getSignerPubKey()); var dummyAEW = new AccountAgeWitness(dummyAccountAgeWitnessHash, orphanedSignedWitness.getDate() - diff --git a/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java index e71b7a4e61..c939a1bf83 100644 --- a/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java +++ b/desktop/src/main/java/bisq/desktop/main/account/content/PaymentAccountsView.java @@ -16,6 +16,8 @@ import bisq.core.payment.PaymentAccount; import bisq.core.payment.payload.PaymentMethod; import bisq.common.UserThread; +import bisq.common.app.DevEnv; +import bisq.common.util.Utilities; import org.apache.commons.lang3.StringUtils; @@ -26,10 +28,14 @@ import javafx.scene.control.Label; import javafx.scene.control.ListCell; import javafx.scene.control.ListView; import javafx.scene.image.ImageView; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.beans.value.ChangeListener; +import javafx.event.EventHandler; + import javafx.collections.ObservableList; import javafx.util.Callback; @@ -41,8 +47,8 @@ public abstract class PaymentAccountsView paymentAccountsListView; private ChangeListener paymentAccountChangeListener; protected Button addAccountButton, exportButton, importButton; - SignedWitnessService signedWitnessService; protected AccountAgeWitnessService accountAgeWitnessService; + private EventHandler keyEventEventHandler; public PaymentAccountsView(M model, AccountAgeWitnessService accountAgeWitnessService) { super(model); @@ -51,6 +57,14 @@ public abstract class PaymentAccountsView { + if (Utilities.isCtrlShiftPressed(KeyCode.L, event)) { + accountAgeWitnessService.getAccountAgeWitnessUtils().logSignedWitnesses(); + } else if (Utilities.isCtrlShiftPressed(KeyCode.S, event)) { + accountAgeWitnessService.getAccountAgeWitnessUtils().logSigners(); + } + }; + buildForm(); paymentAccountChangeListener = (observable, oldValue, newValue) -> { if (newValue != null) @@ -68,6 +82,8 @@ public abstract class PaymentAccountsView addNewAccount()); exportButton.setOnAction(event -> exportAccounts()); importButton.setOnAction(event -> importAccounts()); + if (root.getScene() != null) + root.getScene().addEventHandler(KeyEvent.KEY_RELEASED, keyEventEventHandler); } @Override @@ -76,6 +92,8 @@ public abstract class PaymentAccountsView