fixes based on refactored tests (#454)

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

View file

@ -111,7 +111,9 @@ public class CoreOffersService {
.filter(o -> !o.isMyOffer(keyRing)) .filter(o -> !o.isMyOffer(keyRing))
.filter(o -> { .filter(o -> {
Result result = offerFilter.canTakeOffer(o, coreContext.isApiUser()); 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(() -> .findAny().orElseThrow(() ->
new IllegalStateException(format("offer with id '%s' not found", id))); new IllegalStateException(format("offer with id '%s' not found", id)));

View file

@ -155,7 +155,7 @@ public class XmrWalletService {
public MoneroWallet getWallet() { public MoneroWallet getWallet() {
State state = walletsSetup.getWalletConfig().state(); 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; return wallet;
} }

View file

@ -224,6 +224,6 @@ public class OfferFilterService {
public boolean hasValidSignature(Offer offer) { public boolean hasValidSignature(Offer offer) {
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()); Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
if (arbitrator == null) return false; // invalid arbitrator 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.Task;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import static com.google.common.base.Preconditions.checkNotNull;
public class AddToOfferBook extends Task<PlaceOfferModel> { public class AddToOfferBook extends Task<PlaceOfferModel> {
public AddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) { public AddToOfferBook(TaskRunner<PlaceOfferModel> taskHandler, PlaceOfferModel model) {
@ -33,6 +35,7 @@ public class AddToOfferBook extends Task<PlaceOfferModel> {
protected void run() { protected void run() {
try { try {
runInterceptHook(); runInterceptHook();
checkNotNull(model.getSignOfferResponse().getSignedOfferPayload().getArbitratorSignature(), "Offer's arbitrator signature is null: " + model.getOffer().getId());
model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), model.getOfferBookService().addOffer(new Offer(model.getSignOfferResponse().getSignedOfferPayload()),
() -> { () -> {
model.setOfferAddedToOfferBook(true); model.setOfferAddedToOfferBook(true);

View file

@ -38,9 +38,11 @@ public class MakerProcessSignOfferResponse extends Task<PlaceOfferModel> {
try { try {
runInterceptHook(); runInterceptHook();
// validate arbitrator signature // get arbitrator
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null"); 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"); 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) { public static MoneroTxWallet arbitratorCreatesDisputedPayoutTx(Contract contract, Dispute dispute, DisputeResult disputeResult, MoneroWallet multisigWallet) {
// multisig wallet must be synced // 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 // collect winner and loser payout address and amounts
String winnerPayoutAddress = disputeResult.getWinner() == Winner.BUYER ? 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), SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
// deposit published // 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), ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
DEPOSIT_TXS_SEEN_IN_BLOCKCHAIN(Phase.DEPOSITS_PUBLISHED),
// deposit confirmed // deposit confirmed
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN(Phase.DEPOSITS_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.crypto.Sig;
import bisq.common.util.Tuple2; import bisq.common.util.Tuple2;
import bisq.core.btc.wallet.XmrWalletService; import bisq.core.btc.wallet.XmrWalletService;
import bisq.core.offer.Offer;
import bisq.core.offer.OfferPayload; import bisq.core.offer.OfferPayload;
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator; import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
import bisq.core.trade.messages.InitTradeRequest; import bisq.core.trade.messages.InitTradeRequest;
@ -74,18 +75,21 @@ public class TradeUtils {
/** /**
* Check if the arbitrator signature for an offer is valid. * 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 * @param arbitrator is the possible original arbitrator
* @return true if the arbitrator's signature is valid for the offer * @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 // remove arbitrator signature from signed payload
String signature = signedOfferPayload.getArbitratorSignature(); String signature = offerPayloadCopy.getArbitratorSignature();
signedOfferPayload.setArbitratorSignature(null); offerPayloadCopy.setArbitratorSignature(null);
// get unsigned offer payload as json string // get unsigned offer payload as json string
String unsignedOfferAsJson = JsonUtil.objectToJson(signedOfferPayload); String unsignedOfferAsJson = JsonUtil.objectToJson(offerPayloadCopy);
// verify arbitrator signature // verify arbitrator signature
boolean isValid = true; boolean isValid = true;
@ -95,9 +99,6 @@ public class TradeUtils {
isValid = false; isValid = false;
} }
// replace signature
signedOfferPayload.setArbitratorSignature(signature);
// return result // return result
return isValid; return isValid;
} }

View file

@ -335,7 +335,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
latchTrade(); latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response); Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(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) .with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress() .from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks( .setup(tasks(

View file

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

View file

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

View file

@ -85,78 +85,76 @@ public class DisputeChatPopup {
} }
public void openChat(Dispute selectedDispute, DisputeSession concreteDisputeSession, String counterpartyName) { public void openChat(Dispute selectedDispute, DisputeSession concreteDisputeSession, String counterpartyName) {
UserThread.execute(() -> { closeChat();
closeChat(); this.selectedDispute = selectedDispute;
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)); selectedDispute.getChatMessages().forEach(m -> m.setWasDisplayed(true));
disputeManager.requestPersistence(); disputeManager.requestPersistence();
chatPopupStage = null;
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));
}); });
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. 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 //TODO decrease limits again after testing
private static final int SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(180); private static final int SOCKET_TIMEOUT = (int) TimeUnit.SECONDS.toMillis(180);
private static final int MAX_CONNECTION_THREADS = 10;
public static int getPermittedMessageSize() { public static int getPermittedMessageSize() {
return PERMITTED_MESSAGE_SIZE; return PERMITTED_MESSAGE_SIZE;
@ -131,7 +130,6 @@ public class Connection implements HasCapabilities, Runnable, MessageListener {
@Getter @Getter
private final String uid; private final String uid;
private final ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, "Connection.java executor-service")); 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 // holder of state shared between InputHandler and Connection
@Getter @Getter