fixes based on refactored tests ()

log trade id with error
open dispute chat on user thread
fix null arbitrator signature by copying offer payload before modifying
This commit is contained in:
woodser 2022-09-29 09:46:21 -04:00 committed by GitHub
parent 6209c9578a
commit 93472b9759
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 94 additions and 89 deletions
core/src/main/java/bisq/core
desktop/src/main/java/bisq/desktop/main/support/dispute
p2p/src/main/java/bisq/network/p2p/network

View file

@ -111,7 +111,9 @@ public class CoreOffersService {
.filter(o -> !o.isMyOffer(keyRing))
.filter(o -> {
Result result = offerFilter.canTakeOffer(o, coreContext.isApiUser());
return result.isValid() || result == Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
boolean valid = result.isValid() || result == Result.HAS_NO_PAYMENT_ACCOUNT_VALID_FOR_OFFER;
if (!valid) log.warn("Cannot take offer " + o.getId() + " with invalid state : " + result);
return valid;
})
.findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id)));

View file

@ -155,7 +155,7 @@ public class XmrWalletService {
public MoneroWallet getWallet() {
State state = walletsSetup.getWalletConfig().state();
checkState(state == State.STARTING || state == State.RUNNING, "Cannot call until startup is complete");
checkState(state == State.STARTING || state == State.RUNNING, "Cannot call until startup is complete, but state is: " + state);
return wallet;
}

View file

@ -224,6 +224,6 @@ public class OfferFilterService {
public boolean hasValidSignature(Offer offer) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator
return TradeUtils.isArbitratorSignatureValid(offer.getOfferPayload(), arbitrator);
return TradeUtils.isArbitratorSignatureValid(offer, arbitrator);
}
}

View file

@ -23,6 +23,8 @@ import bisq.core.offer.placeoffer.PlaceOfferModel;
import bisq.common.taskrunner.Task;
import bisq.common.taskrunner.TaskRunner;
import static com.google.common.base.Preconditions.checkNotNull;
public class AddToOfferBook extends Task<PlaceOfferModel> {
public AddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
@ -33,6 +35,7 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
protected void run() {
try {
runInterceptHook();
checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOffer().getId());
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
() -> {
model.setOfferAddedToOfferBook(true);

View file

@ -38,9 +38,11 @@ public class MakerProcessSignOfferResponse extends Task<PlaceOfferModel> {
try {
runInterceptHook();
// validate arbitrator signature
// get arbitrator
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null");
if (!TradeUtils.isArbitratorSignatureValid(model.getSignOfferResponse().getSignedOfferPayload(), arbitrator)) {
// validate arbitrator signature
if (!TradeUtils.isArbitratorSignatureValid(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), arbitrator)) {
throw new RuntimeException("Offer payload has invalid arbitrator signature");
}

View file

@ -669,7 +669,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) {
// multisig wallet must be synced
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx");
if (multisigWallet.isMultisigImportNeeded()) throw new RuntimeException("Arbitrator's wallet needs updated multisig hex to create payout tx which means a trader must have already broadcast the payout tx for trade " + dispute.getTradeId());
// collect winner and loser payout address and amounts
String winnerPayoutAddress = disputeResult.getWinner() == Winner.BUYER ?

View file

@ -127,8 +127,8 @@ public abstract class Trade implements Tradable, Model {
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
// deposit published
DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED), // TODO: seeing in network usually happens after arbitrator publishes
ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED),
// deposit confirmed
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN(Phase.DEPOSITS_CONFIRMED),

View file

@ -23,6 +23,7 @@ import bisq.common.crypto.PubKeyRing;
import bisq.common.crypto.Sig;
import bisq.common.util.Tuple2;
import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.trade.messages.InitTradeRequest;
@ -74,18 +75,21 @@ public class TradeUtils {
/**
* Check if the arbitrator signature for an offer is valid.
*
* @param signedOfferPayload is a signed offer payload
* @param offer is a signed offer with payload
* @param arbitrator is the possible original arbitrator
* @return true if the arbitrator's signature is valid for the offer
*/
public static boolean isArbitratorSignatureValid(OfferPayload signedOfferPayload, Arbitrator arbitrator) {
public static boolean isArbitratorSignatureValid(Offer offer, Arbitrator arbitrator) {
// copy offer payload
OfferPayload offerPayloadCopy = OfferPayload.fromProto(offer.toProtoMessage().getOfferPayload());
// remove arbitrator signature from signed payload
String signature = signedOfferPayload.getArbitratorSignature();
signedOfferPayload.setArbitratorSignature(null);
String signature = offerPayloadCopy.getArbitratorSignature();
offerPayloadCopy.setArbitratorSignature(null);
// get unsigned offer payload as json string
String unsignedOfferAsJson = JsonUtil.objectToJson(signedOfferPayload);
String unsignedOfferAsJson = JsonUtil.objectToJson(offerPayloadCopy);
// verify arbitrator signature
boolean isValid = true;
@ -95,9 +99,6 @@ public class TradeUtils {
isValid = false;
}
// replace signature
signedOfferPayload.setArbitratorSignature(signature);
// return result
return isValid;
}

View file

@ -335,7 +335,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST)
expect(anyState(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST, Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS, Trade.State.DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(

View file

@ -34,7 +34,7 @@ public class ProcessDepositResponse extends TradeTask {
protected void run() {
try {
runInterceptHook();
trade.setState(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
processModel.getTradeManager().requestPersistence();
complete();
} catch (Throwable t) {

View file

@ -50,6 +50,7 @@ public class ProcessSignContractResponse extends TradeTask {
if (!contractAsJson.equals(response.getContractAsJson())) {
trade.getContract().printDiff(response.getContractAsJson());
failed("Contracts are not matching");
return;
}
// get peer info

View file

@ -85,78 +85,76 @@ public class DisputeChatPopup {
}
public void openChat(Dispute selectedDispute, DisputeSession concreteDisputeSession, String counterpartyName) {
UserThread.execute(() -> {
closeChat();
this.selectedDispute = selectedDispute;
closeChat();
this.selectedDispute = selectedDispute;
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
ChatView chatView = new ChatView(disputeManager, formatter, counterpartyName);
chatView.setAllowAttachments(true);
chatView.setDisplayHeader(false);
chatView.initialize();
AnchorPane pane = new AnchorPane(chatView);
pane.setPrefSize(760, 500);
AnchorPane.setLeftAnchor(chatView, 10d);
AnchorPane.setRightAnchor(chatView, 10d);
AnchorPane.setTopAnchor(chatView, -20d);
AnchorPane.setBottomAnchor(chatView, 10d);
pane.getStyleClass().add("dispute-chat-border");
Button closeDisputeButton = null;
if (!selectedDispute.isClosed() && !disputeManager.isTrader(selectedDispute)) {
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
}
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
chatView.activate();
chatView.scrollToBottom();
chatPopupStage = new Stage();
chatPopupStage.setTitle(Res.get("disputeChat.chatWindowTitle", selectedDispute.getShortTradeId())
+ " " + selectedDispute.getRoleString());
StackPane owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
chatPopupStage.initOwner(rootScene.getWindow());
chatPopupStage.initModality(Modality.NONE);
chatPopupStage.initStyle(StageStyle.DECORATED);
chatPopupStage.setOnHiding(event -> {
chatView.deactivate();
// at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon.
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
ChatView chatView = new ChatView(disputeManager, formatter, counterpartyName);
chatView.setAllowAttachments(true);
chatView.setDisplayHeader(false);
chatView.initialize();
AnchorPane pane = new AnchorPane(chatView);
pane.setPrefSize(760, 500);
AnchorPane.setLeftAnchor(chatView, 10d);
AnchorPane.setRightAnchor(chatView, 10d);
AnchorPane.setTopAnchor(chatView, -20d);
AnchorPane.setBottomAnchor(chatView, 10d);
pane.getStyleClass().add("dispute-chat-border");
Button closeDisputeButton = null;
if (!selectedDispute.isClosed() && !disputeManager.isTrader(selectedDispute)) {
closeDisputeButton = new AutoTooltipButton(Res.get("support.closeTicket"));
closeDisputeButton.setOnAction(e -> chatCallback.onCloseDisputeFromChatWindow(selectedDispute));
}
chatView.display(concreteDisputeSession, closeDisputeButton, pane.widthProperty());
chatView.activate();
chatView.scrollToBottom();
chatPopupStage = new Stage();
chatPopupStage.setTitle(Res.get("disputeChat.chatWindowTitle", selectedDispute.getShortTradeId())
+ " " + selectedDispute.getRoleString());
StackPane owner = MainView.getRootContainer();
Scene rootScene = owner.getScene();
chatPopupStage.initOwner(rootScene.getWindow());
chatPopupStage.initModality(Modality.NONE);
chatPopupStage.initStyle(StageStyle.DECORATED);
chatPopupStage.setOnHiding(event -> {
chatView.deactivate();
// at close we set all as displayed. While open we ignore updates of the numNewMsg in the list icon.
selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence();
chatPopupStage = null;
});
Scene scene = new Scene(pane);
CssTheme.loadSceneStyles(scene, preferences.getCssTheme(), false);
scene.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
if (ev.getCode() == KeyCode.ESCAPE) {
ev.consume();
chatPopupStage.hide();
}
});
chatPopupStage.setScene(scene);
chatPopupStage.setOpacity(0);
chatPopupStage.show();
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
chatPopupStage.xProperty().addListener(xPositionListener);
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
chatPopupStage.yProperty().addListener(yPositionListener);
if (chatPopupStageXPosition == -1) {
Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
} else {
chatPopupStage.setX(chatPopupStageXPosition);
chatPopupStage.setY(chatPopupStageYPosition);
}
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1));
chatPopupStage = null;
});
Scene scene = new Scene(pane);
CssTheme.loadSceneStyles(scene, preferences.getCssTheme(), false);
scene.addEventHandler(KeyEvent.KEY_RELEASED, ev -> {
if (ev.getCode() == KeyCode.ESCAPE) {
ev.consume();
chatPopupStage.hide();
}
});
chatPopupStage.setScene(scene);
chatPopupStage.setOpacity(0);
chatPopupStage.show();
xPositionListener = (observable, oldValue, newValue) -> chatPopupStageXPosition = (double) newValue;
chatPopupStage.xProperty().addListener(xPositionListener);
yPositionListener = (observable, oldValue, newValue) -> chatPopupStageYPosition = (double) newValue;
chatPopupStage.yProperty().addListener(yPositionListener);
if (chatPopupStageXPosition == -1) {
Window rootSceneWindow = rootScene.getWindow();
double titleBarHeight = rootSceneWindow.getHeight() - rootScene.getHeight();
chatPopupStage.setX(Math.round(rootSceneWindow.getX() + (owner.getWidth() - chatPopupStage.getWidth() / 4 * 3)));
chatPopupStage.setY(Math.round(rootSceneWindow.getY() + titleBarHeight + (owner.getHeight() - chatPopupStage.getHeight() / 4 * 3)));
} else {
chatPopupStage.setX(chatPopupStageXPosition);
chatPopupStage.setY(chatPopupStageYPosition);
}
// Delay display to next render frame to avoid that the popup is first quickly displayed in default position
// and after a short moment in the correct position
UserThread.execute(() -> chatPopupStage.setOpacity(1));
}
}

View file

@ -112,7 +112,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
private static final int MAX_PERMITTED_MESSAGE_SIZE = 10 * 1024 * 1024; // 10 MB (425 offers resulted in about 660 kb, mailbox msg will add more to it) offer has usually 2 kb, mailbox 3kb.
//TODO decrease limits again after testing
private static final int SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(180);
private static final int MAX_CONNECTION_THREADS = 10;
public static int getPermittedMessageSize() {
return PERMITTED_MESSAGE_SIZE;
@ -131,7 +130,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
@Getter
private final String uid;
private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Connection.java executor-service"));
private final ExecutorService connectionThreadPool = Executors.newFixedThreadPool(MAX_CONNECTION_THREADS);
// holder of state shared between InputHandler and Connection
@Getter