add multisig wallet state and wait for multisig to complete

refactor trade protocol
This commit is contained in:
woodser 2022-07-30 12:36:52 -04:00
parent f61fd09127
commit 50126874a0
17 changed files with 305 additions and 748 deletions

View file

@ -107,7 +107,10 @@ public abstract class Trade implements Tradable, Model {
// #################### Phase INIT
// When trade protocol starts no funds are on stake
PREPARATION(Phase.INIT),
CONTRACT_SIGNATURE_REQUESTED(Phase.INIT), // TODO (woodser): add more states for initializing multisig, etc to support trade initialization notifications
MULTISIG_PREPARED(Phase.INIT),
MULTISIG_MADE(Phase.INIT),
MULTISIG_COMPLETED(Phase.INIT),
CONTRACT_SIGNATURE_REQUESTED(Phase.INIT),
CONTRACT_SIGNED(Phase.INIT),
// At first part maker/taker have different roles
@ -894,7 +897,6 @@ public abstract class Trade implements Tradable, Model {
// create block listener
depositTxListener = new MoneroWalletListener() {
Long unlockHeight = null;
@Override
@ -903,6 +905,9 @@ public abstract class Trade implements Tradable, Model {
// ignore if no longer listening
if (depositTxListener == null) return;
// use latest height
height = havenoWallet.getHeight();
// ignore if before unlock height
if (unlockHeight != null && height < unlockHeight) return;
@ -921,7 +926,7 @@ public abstract class Trade implements Tradable, Model {
if (unlockHeight == null && txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
}
// check if deposit txs unlocked
if (unlockHeight != null && height >= unlockHeight) {
log.info("Multisig deposits unlocked for trade {}", getId());

View file

@ -3,16 +3,15 @@ package bisq.core.trade.protocol;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.DepositRequest;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ArbitratorSendsInitTradeAndMultisigRequests;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesDepositRequest;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ArbitratorProcessesReserveTx;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
@ -59,60 +58,12 @@ public class ArbitratorProtocol extends DisputeProtocol {
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleSignContractRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
log.warn("Arbitrator ignoring SignContractResponse");
}
public void handleDepositRequest(DepositRequest request, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleDepositRequest()");
System.out.println("ArbitratorProtocol.handleDepositRequest() " + trade.getId());
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
@ -138,6 +89,16 @@ public class ArbitratorProtocol extends DisputeProtocol {
awaitTradeLatch();
}
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
log.warn("Arbitrator ignoring DepositResponse");
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
log.warn("Arbitrator ignoring PaymentAccountPayloadRequest");
}
///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher

View file

@ -28,30 +28,22 @@ import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.PaymentReceivedMessage;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerProcessDelayedPayoutTxSignatureRequest;
import bisq.core.trade.protocol.tasks.buyer.BuyerSendsDelayedPayoutTxSignatureResponse;
import bisq.core.trade.protocol.tasks.buyer.BuyerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.buyer.BuyerVerifiesPreparedDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
@Slf4j
public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol {
@ -68,8 +60,6 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
@ -99,165 +89,30 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
awaitTradeLatch();
}
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleInitMultisigRequest(request, sender);
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractRequest(message, sender);
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract response after contract signed
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractResponse(message, sender);
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleDepositResponse(response, sender);
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), request);
if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class,
MakerRemovesOpenOffer.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null;
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock
});
}
}
super.handlePaymentAccountPayloadRequest(request, sender);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -297,7 +152,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
.with(message)
.from(peer))
.setup(tasks(
MakerRemovesOpenOffer.class,
MaybeRemoveOpenOffer.class,
BuyerProcessDelayedPayoutTxSignatureRequest.class,
BuyerVerifiesPreparedDelayedPayoutTx.class,
BuyerSignsDelayedPayoutTx.class,

View file

@ -33,12 +33,6 @@ import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.buyer.BuyerFinalizesDelayedPayoutTx;
@ -54,13 +48,11 @@ import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import static com.google.common.base.Preconditions.checkNotNull;
@ -86,8 +78,6 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
@Override
public void onTakeOffer(TradeResultHandler tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
@ -119,161 +109,27 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleInitMultisigRequest(request, sender);
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractRequest(message, sender);
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractResponse(message, sender);
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleDepositResponse(response, sender);
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), request);
if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null;
tradeResultHandler.handleResult(trade); // trade is initialized
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock
});
}
}
super.handlePaymentAccountPayloadRequest(request, sender);
}
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -142,8 +142,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
.using(new TradeTaskRunner(trade,
() -> {
this.errorMessageHandler = null;
resultHandler.handleResult();
handleTaskRunnerSuccess(event);
resultHandler.handleResult();
},
(errorMessage) -> {
handleTaskRunnerFault(event, errorMessage);

View file

@ -183,10 +183,6 @@ public class ProcessModel implements Model, PersistablePayload {
@Setter
private String multisigAddress;
@Nullable
@Getter
@Setter
private boolean multisigSetupComplete; // TODO (woodser): redundant with multisigAddress existing, remove
// We want to indicate the user the state of the message delivery of the
// PaymentSentMessage. As well we do an automatic re-send in case it was not ACKed yet.
@ -247,7 +243,6 @@ public class ProcessModel implements Model, PersistablePayload {
Optional.ofNullable(preparedMultisigHex).ifPresent(e -> builder.setPreparedMultisigHex(preparedMultisigHex));
Optional.ofNullable(madeMultisigHex).ifPresent(e -> builder.setMadeMultisigHex(madeMultisigHex));
Optional.ofNullable(multisigAddress).ifPresent(e -> builder.setMultisigAddress(multisigAddress));
Optional.ofNullable(multisigSetupComplete).ifPresent(e -> builder.setMultisigSetupComplete(multisigSetupComplete));
return builder.build();
}
@ -279,7 +274,6 @@ public class ProcessModel implements Model, PersistablePayload {
processModel.setPreparedMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getPreparedMultisigHex()));
processModel.setMadeMultisigHex(ProtoUtil.stringOrNullFromProto(proto.getMadeMultisigHex()));
processModel.setMultisigAddress(ProtoUtil.stringOrNullFromProto(proto.getMultisigAddress()));
processModel.setMultisigSetupComplete(proto.getMultisigSetupComplete());
String paymentStartedMessageStateString = ProtoUtil.stringOrNullFromProto(proto.getPaymentStartedMessageState());
MessageState paymentStartedMessageState = ProtoUtil.enumFromProto(MessageState.class, paymentStartedMessageStateString);

View file

@ -21,23 +21,17 @@ package bisq.core.trade.protocol;
import bisq.core.trade.SellerAsMakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InitTradeRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSendsInitTradeRequestIfUnreserved;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
@ -45,13 +39,11 @@ import bisq.core.trade.protocol.tasks.seller.SellerSendDelayedPayoutTxSignatureR
import bisq.core.trade.protocol.tasks.seller.SellerSignsDelayedPayoutTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerFinalizesDepositTx;
import bisq.core.trade.protocol.tasks.seller_as_maker.SellerAsMakerProcessDepositTxMessage;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
@Slf4j
public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtocol {
@ -68,8 +60,6 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
@ -99,164 +89,30 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
awaitTradeLatch();
}
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleInitMultisigRequest(request, sender);
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractRequest(message, sender);
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractResponse(message, sender);
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleDepositResponse(response, sender);
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), request);
if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class,
MakerRemovesOpenOffer.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null;
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock
});
}
}
super.handlePaymentAccountPayloadRequest(request, sender);
}
///////////////////////////////////////////////////////////////////////////////////////////
@ -301,7 +157,7 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
.with(message)
.from(peer))
.setup(tasks(
MakerRemovesOpenOffer.class,
MaybeRemoveOpenOffer.class,
SellerAsMakerProcessDepositTxMessage.class,
SellerAsMakerFinalizesDepositTx.class,
SellerCreatesDelayedPayoutTx.class,

View file

@ -23,20 +23,14 @@ import bisq.core.trade.SellerAsTakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.InputsForDepositTxResponse;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.protocol.tasks.ApplyFilter;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.SendSignContractRequestAfterMultisig;
import bisq.core.trade.protocol.tasks.TradeTask;
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
@ -48,14 +42,12 @@ import bisq.core.trade.protocol.tasks.taker.TakerPublishFeeTx;
import bisq.core.trade.protocol.tasks.taker.TakerReservesTradeFunds;
import bisq.core.trade.protocol.tasks.taker.TakerSendsInitTradeRequestToArbitrator;
import bisq.core.trade.protocol.tasks.taker.TakerVerifyMakerFeePayment;
import bisq.core.util.Validator;
import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
import bisq.common.handlers.ResultHandler;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import static com.google.common.base.Preconditions.checkNotNull;
@ -79,8 +71,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
// Take offer
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
@Override
public void onTakeOffer(TradeResultHandler tradeResultHandler,
ErrorMessageHandler errorMessageHandler) {
@ -112,164 +102,29 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleInitMultisigRequest(request, sender);
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after contract signature requested
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNATURE_REQUESTED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractRequest(message, sender);
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
super.handleSignContractResponse(message, sender);
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
super.handleDepositResponse(response, sender);
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), request);
if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null;
tradeResultHandler.handleResult(trade); // trade is initialized
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock
});
}
}
super.handlePaymentAccountPayloadRequest(request, sender);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -136,13 +136,13 @@ public abstract class SellerProtocol extends DisputeProtocol {
getVerifyPeersFeePaymentClass(),
SellerPreparesPaymentReceivedMessage.class,
SellerSendsPaymentReceivedMessage.class)
.using(new TradeTaskRunner(trade, () -> {
this.errorMessageHandler = null;
resultHandler.handleResult();
handleTaskRunnerSuccess(event);
}, (errorMessage) -> {
handleTaskRunnerFault(event, errorMessage);
})))
.using(new TradeTaskRunner(trade, () -> {
this.errorMessageHandler = null;
handleTaskRunnerSuccess(event);
resultHandler.handleResult();
}, (errorMessage) -> {
handleTaskRunnerFault(event, errorMessage);
})))
.run(() -> trade.setState(Trade.State.SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT))
.executeTasks();
awaitTradeLatch();

View file

@ -23,12 +23,22 @@ import bisq.core.trade.TradeManager;
import bisq.core.trade.TradeUtils;
import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.PaymentSentMessage;
import bisq.core.trade.messages.DepositResponse;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.messages.TradeMessage;
import bisq.core.trade.messages.UpdateMultisigRequest;
import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
import bisq.core.trade.protocol.tasks.ProcessPaymentAccountPayloadRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer;
import bisq.core.util.Validator;
import bisq.network.p2p.AckMessage;
@ -54,7 +64,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.fxmisc.easybind.EasyBind;
import javax.annotation.Nullable;
@Slf4j
@ -213,8 +223,162 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer);
public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer);
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
MaybeSendSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
}
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.MULTISIG_COMPLETED || trade.getState() == Trade.State.CONTRACT_SIGNATURE_REQUESTED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyState(Trade.State.MULTISIG_COMPLETED, Trade.State.CONTRACT_SIGNATURE_REQUESTED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract request after multisig created
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MULTISIG_COMPLETED) new Thread(() -> handleSignContractRequest(message, sender)).start(); // process notification without trade lock
});
}
}
}
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), message);
if (trade.getState() == Trade.State.CONTRACT_SIGNED) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(state(Trade.State.CONTRACT_SIGNED)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT)) // extend timeout
.executeTasks(true);
awaitTradeLatch();
} else {
// process sign contract response after contract signed
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.CONTRACT_SIGNED) new Thread(() -> handleSignContractResponse(message, sender)).start(); // process notification without trade lock
});
}
}
}
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
synchronized (trade) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(state(Trade.State.MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
startTimeout(TRADE_TIMEOUT);
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
}
}
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountPayloadRequest()");
synchronized (trade) {
Validator.checkTradeId(processModel.getOfferId(), request);
if (trade.getState() == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) {
latchTrade();
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(state(Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class,
MaybeRemoveOpenOffer.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
this.errorMessageHandler = null;
handleTaskRunnerSuccess(sender, request);
if (tradeResultHandler != null) tradeResultHandler.handleResult(trade); // trade is initialized
},
errorMessage -> {
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(TRADE_TIMEOUT))
.executeTasks(true);
awaitTradeLatch();
} else {
EasyBind.subscribe(trade.stateProperty(), state -> {
if (state == Trade.State.MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG) new Thread(() -> handlePaymentAccountPayloadRequest(request, sender)).start(); // process notification without trade lock
});
}
}
}
// TODO (woodser): update to use fluent for consistency
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
@ -237,7 +401,6 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
awaitTradeLatch();
}
///////////////////////////////////////////////////////////////////////////////////////////
// FluentProtocol
///////////////////////////////////////////////////////////////////////////////////////////

View file

@ -20,6 +20,7 @@ package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version;
import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.trade.ArbitratorTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.Trade.State;
import bisq.core.trade.messages.SignContractRequest;
@ -32,13 +33,13 @@ import monero.wallet.model.MoneroTxWallet;
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
@Slf4j
public class SendSignContractRequestAfterMultisig extends TradeTask {
public class MaybeSendSignContractRequest extends TradeTask {
private boolean ack1 = false; // TODO (woodser) these represent onArrived(), not the ack
private boolean ack2 = false;
@SuppressWarnings({"unused"})
public SendSignContractRequestAfterMultisig(TaskRunner taskHandler, Trade trade) {
public MaybeSendSignContractRequest(TaskRunner taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -46,11 +47,17 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
protected void run() {
try {
runInterceptHook();
// skip if arbitrator
if (trade instanceof ArbitratorTrade) {
complete();
return;
}
// skip if multisig wallet not complete
if (!processModel.isMultisigSetupComplete()) {
if (processModel.getMultisigAddress() == null) {
complete();
return; // TODO: woodser: this does not ack original request?
return;
}
// skip if deposit tx already created
@ -71,7 +78,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
// save process state
processModel.setDepositTxXmr(depositTx);
processModel.setDepositTxXmr(depositTx); // TODO: trade.getSelf().setDepositTx()
trade.getSelf().setDepositTxHash(depositTx.getHash());
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address?

View file

@ -23,9 +23,7 @@ import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.protocol.TradeListener;
import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener;
@ -92,8 +90,9 @@ public class ProcessInitMultisigRequest extends TradeTask {
log.info("Preparing multisig wallet for trade {}", trade.getId());
multisigWallet = xmrWalletService.createMultisigWallet(trade.getId());
processModel.setPreparedMultisigHex(multisigWallet.prepareMultisig());
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_PREPARED);
updateParticipants = true;
} else if (!processModel.isMultisigSetupComplete()) {
} else if (processModel.getMultisigAddress() == null) {
multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
}
@ -103,15 +102,16 @@ public class ProcessInitMultisigRequest extends TradeTask {
log.info("Making multisig wallet for trade {}", trade.getId());
MoneroMultisigInitResult result = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)?
processModel.setMadeMultisigHex(result.getMultisigHex());
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_MADE);
updateParticipants = true;
}
// exchange multisig keys if applicable
if (!processModel.isMultisigSetupComplete() && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
if (processModel.getMultisigAddress() == null && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
log.info("Exchanging multisig wallet keys for trade {}", trade.getId());
multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword());
processModel.setMultisigSetupComplete(true); // TODO: (woodser): remove this field?
processModel.setMultisigAddress(multisigWallet.getPrimaryAddress());
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId()); // save and close multisig wallet once it's created
}

View file

@ -17,6 +17,7 @@
package bisq.core.trade.protocol.tasks.maker;
import bisq.core.trade.MakerTrade;
import bisq.core.trade.Trade;
import bisq.core.trade.protocol.tasks.TradeTask;
@ -27,8 +28,8 @@ import lombok.extern.slf4j.Slf4j;
import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j
public class MakerRemovesOpenOffer extends TradeTask {
public MakerRemovesOpenOffer(TaskRunner<Trade> taskHandler, Trade trade) {
public class MaybeRemoveOpenOffer extends TradeTask {
public MaybeRemoveOpenOffer(TaskRunner<Trade> taskHandler, Trade trade) {
super(taskHandler, trade);
}
@ -36,8 +37,10 @@ public class MakerRemovesOpenOffer extends TradeTask {
protected void run() {
try {
runInterceptHook();
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
if (trade instanceof MakerTrade) {
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
}
complete();
} catch (Throwable t) {

View file

@ -128,8 +128,7 @@ class GrpcTradesService extends TradesImplBase {
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled())
errorMessageHandler.handleErrorMessage(errorMessage);
if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage);
});
} catch (Throwable cause) {
cause.printStackTrace();
@ -169,7 +168,7 @@ class GrpcTradesService extends TradesImplBase {
responseObserver.onCompleted();
},
errorMessage -> {
if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage);
if (!errorMessageHandler.isErrorHandled()) errorMessageHandler.handleErrorMessage(errorMessage);
});
} catch (Throwable cause) {
cause.printStackTrace();

View file

@ -43,7 +43,7 @@ import bisq.core.trade.protocol.tasks.buyer_as_maker.BuyerAsMakerSendsInputsForD
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerCreatesDepositTxInputs;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSendsDepositTxMessage;
import bisq.core.trade.protocol.tasks.buyer_as_taker.BuyerAsTakerSignsDepositTx;
import bisq.core.trade.protocol.tasks.maker.MakerRemovesOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MaybeRemoveOpenOffer;
import bisq.core.trade.protocol.tasks.maker.MakerSetsLockTime;
import bisq.core.trade.protocol.tasks.maker.MakerVerifyTakerFeePayment;
import bisq.core.trade.protocol.tasks.seller.SellerCreatesDelayedPayoutTx;
@ -157,7 +157,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
BuyerAsMakerSendsInputsForDepositTxResponse.class,
BuyerProcessDelayedPayoutTxSignatureRequest.class,
MakerRemovesOpenOffer.class,
MaybeRemoveOpenOffer.class,
BuyerVerifiesPreparedDelayedPayoutTx.class,
BuyerSignsDelayedPayoutTx.class,
BuyerSendsDelayedPayoutTxSignatureResponse.class,
@ -215,7 +215,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
SellerAsMakerSendsInputsForDepositTxResponse.class,
//SellerAsMakerProcessDepositTxMessage.class,
MakerRemovesOpenOffer.class,
MaybeRemoveOpenOffer.class,
SellerAsMakerFinalizesDepositTx.class,
SellerCreatesDelayedPayoutTx.class,
SellerSendDelayedPayoutTxSignatureRequest.class,

View file

@ -403,6 +403,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
// #################### Phase PREPARATION
case PREPARATION:
case CONTRACT_SIGNATURE_REQUESTED:
case CONTRACT_SIGNED:
sellerState.set(UNDEFINED);
buyerState.set(BuyerState.UNDEFINED);
break;

View file

@ -1624,43 +1624,46 @@ message Trade {
enum State {
PB_ERROR_STATE = 0;
PREPARATION = 1;
CONTRACT_SIGNATURE_REQUESTED = 2;
CONTRACT_SIGNED = 3;
TAKER_PUBLISHED_TAKER_FEE_TX = 4;
MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST = 5;
MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST = 6;
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 7;
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 8;
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 9;
ARBITRATOR_PUBLISHED_DEPOSIT_TX = 10;
TAKER_SAW_DEPOSIT_TX_IN_NETWORK = 11;
TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 12;
TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 13;
TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 14;
TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 15;
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 16;
MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 17;
DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN = 18;
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 19;
BUYER_SENT_PAYMENT_SENT_MSG = 20;
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 21;
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 22;
BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 23;
SELLER_RECEIVED_PAYMENT_SENT_MSG = 24;
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 25;
SELLER_SENT_PAYMENT_RECEIVED_MSG = 26;
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 27;
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 28;
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 29;
SELLER_PUBLISHED_PAYOUT_TX = 30;
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 31;
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 32;
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 33;
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 34;
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 35;
BUYER_SAW_PAYOUT_TX_IN_NETWORK = 36;
BUYER_PUBLISHED_PAYOUT_TX = 37;
WITHDRAW_COMPLETED = 38;
MULTISIG_PREPARED = 2;
MULTISIG_MADE = 3;
MULTISIG_COMPLETED = 4;
CONTRACT_SIGNATURE_REQUESTED = 5;
CONTRACT_SIGNED = 6;
TAKER_PUBLISHED_TAKER_FEE_TX = 7;
MAKER_SENT_PUBLISH_DEPOSIT_TX_REQUEST = 8;
MAKER_SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST = 9;
MAKER_STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 10;
MAKER_SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 11;
TAKER_RECEIVED_PUBLISH_DEPOSIT_TX_REQUEST = 12;
ARBITRATOR_PUBLISHED_DEPOSIT_TX = 13;
TAKER_SAW_DEPOSIT_TX_IN_NETWORK = 14;
TAKER_SENT_DEPOSIT_TX_PUBLISHED_MSG = 15;
TAKER_SAW_ARRIVED_DEPOSIT_TX_PUBLISHED_MSG = 16;
TAKER_STORED_IN_MAILBOX_DEPOSIT_TX_PUBLISHED_MSG = 17;
TAKER_SEND_FAILED_DEPOSIT_TX_PUBLISHED_MSG = 18;
MAKER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG = 19;
MAKER_SAW_DEPOSIT_TX_IN_NETWORK = 20;
DEPOSIT_UNLOCKED_IN_BLOCK_CHAIN = 21;
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 22;
BUYER_SENT_PAYMENT_SENT_MSG = 23;
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 24;
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 25;
BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 26;
SELLER_RECEIVED_PAYMENT_SENT_MSG = 27;
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 28;
SELLER_SENT_PAYMENT_RECEIVED_MSG = 29;
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 30;
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 31;
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 32;
SELLER_PUBLISHED_PAYOUT_TX = 33;
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 34;
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 35;
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 36;
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 37;
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 38;
BUYER_SAW_PAYOUT_TX_IN_NETWORK = 39;
BUYER_PUBLISHED_PAYOUT_TX = 40;
WITHDRAW_COMPLETED = 41;
}
enum Phase {
@ -1789,7 +1792,6 @@ message ProcessModel {
string prepared_multisig_hex = 1007;
string made_multisig_hex = 1008;
string multisig_address = 1009;
bool multisig_setup_complete = 1010; // TODO: remove this field
}
message TradingPeer {