mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-21 12:48:35 -04:00
rename all packages and other names from bisq to haveno
This commit is contained in:
parent
ab0b9e3b77
commit
1a1fb130c0
1775 changed files with 14575 additions and 16767 deletions
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.filter.FilterManager;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ApplyFilter extends TradeTask {
|
||||
public ApplyFilter(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
NodeAddress nodeAddress = checkNotNull(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
FilterManager filterManager = processModel.getFilterManager();
|
||||
if (filterManager.isNodeAddressBanned(nodeAddress)) {
|
||||
failed("Other trader is banned by their node address.\n" +
|
||||
"tradePeerNodeAddress=" + nodeAddress);
|
||||
} else if (filterManager.isOfferIdBanned(trade.getId())) {
|
||||
failed("Offer ID is banned.\n" +
|
||||
"Offer ID=" + trade.getId());
|
||||
} else if (trade.getOffer() != null && filterManager.isCurrencyBanned(trade.getOffer().getCurrencyCode())) {
|
||||
failed("Currency is banned.\n" +
|
||||
"Currency code=" + trade.getOffer().getCurrencyCode());
|
||||
} else if (filterManager.isPaymentMethodBanned(checkNotNull(trade.getOffer()).getPaymentMethod())) {
|
||||
failed("Payment method is banned.\n" +
|
||||
"Payment method=" + trade.getOffer().getPaymentMethod().getId());
|
||||
} else if (filterManager.requireUpdateToNewVersionForTrading()) {
|
||||
failed("Your version of Haveno is not compatible for trading anymore. " +
|
||||
"Please update to the latest Haveno version at https://haveno.network/downloads.");
|
||||
} else {
|
||||
complete();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import common.utils.JsonUtils;
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositRequest;
|
||||
import haveno.core.trade.messages.DepositResponse;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.daemon.MoneroDaemon;
|
||||
import monero.daemon.model.MoneroSubmitTxResult;
|
||||
|
||||
@Slf4j
|
||||
public class ArbitratorProcessDepositRequest extends TradeTask {
|
||||
|
||||
private boolean depositTxsRelayed = false;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ArbitratorProcessDepositRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
MoneroDaemon daemon = trade.getXmrWalletService().getDaemon();
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// get contract and signature
|
||||
String contractAsJson = trade.getContractAsJson();
|
||||
DepositRequest request = (DepositRequest) processModel.getTradeMessage(); // TODO (woodser): verify response
|
||||
String signature = request.getContractSignature();
|
||||
|
||||
// get trader info
|
||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
if (trader == null) throw new RuntimeException(request.getClass().getSimpleName() + " is not from maker, taker, or arbitrator");
|
||||
PubKeyRing peerPubKeyRing = trader.getPubKeyRing();
|
||||
|
||||
// verify signature
|
||||
if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid");
|
||||
|
||||
// set peer's signature
|
||||
trader.setContractSignature(signature);
|
||||
|
||||
// collect expected values
|
||||
Offer offer = trade.getOffer();
|
||||
boolean isFromTaker = trader == trade.getTaker();
|
||||
boolean isFromBuyer = trader == trade.getBuyer();
|
||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||
BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : offer.getAmount();
|
||||
BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
||||
String depositAddress = processModel.getMultisigAddress();
|
||||
|
||||
// verify deposit tx
|
||||
try {
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
tradeFee,
|
||||
sendAmount,
|
||||
securityDeposit,
|
||||
depositAddress,
|
||||
trader.getDepositTxHash(),
|
||||
request.getDepositTxHex(),
|
||||
request.getDepositTxKey(),
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing deposit tx from " + (isFromTaker ? "taker " : "maker ") + trader.getNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
// set deposit info
|
||||
trader.setDepositTxHex(request.getDepositTxHex());
|
||||
trader.setDepositTxKey(request.getDepositTxKey());
|
||||
if (request.getPaymentAccountKey() != null) trader.setPaymentAccountKey(request.getPaymentAccountKey());
|
||||
|
||||
// relay deposit txs when both available
|
||||
// TODO (woodser): add small delay so tx has head start against double spend attempts?
|
||||
if (processModel.getMaker().getDepositTxHex() != null && processModel.getTaker().getDepositTxHex() != null) {
|
||||
|
||||
// relay txs
|
||||
MoneroSubmitTxResult makerResult = daemon.submitTxHex(processModel.getMaker().getDepositTxHex(), true);
|
||||
MoneroSubmitTxResult takerResult = daemon.submitTxHex(processModel.getTaker().getDepositTxHex(), true);
|
||||
if (!makerResult.isGood()) throw new RuntimeException("Error submitting maker deposit tx: " + JsonUtils.serialize(makerResult));
|
||||
if (!takerResult.isGood()) throw new RuntimeException("Error submitting taker deposit tx: " + JsonUtils.serialize(takerResult));
|
||||
daemon.relayTxsByHash(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
||||
depositTxsRelayed = true;
|
||||
|
||||
// update trade state
|
||||
log.info("Arbitrator submitted deposit txs for trade " + trade.getId());
|
||||
trade.setState(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
// create deposit response
|
||||
DepositResponse response = new DepositResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
null);
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
sendDepositResponse(trade.getTaker().getNodeAddress(), trade.getTaker().getPubKeyRing(), response);
|
||||
} else {
|
||||
if (processModel.getMaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from maker for trade " + trade.getId());
|
||||
if (processModel.getTaker().getDepositTxHex() == null) log.info("Arbitrator waiting for deposit request from taker for trade " + trade.getId());
|
||||
}
|
||||
|
||||
complete();
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} catch (Throwable t) {
|
||||
|
||||
// handle error before deposits relayed
|
||||
if (!depositTxsRelayed) {
|
||||
try {
|
||||
daemon.flushTxPool(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash());
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// create deposit response with error
|
||||
DepositResponse response = new DepositResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
t.getMessage());
|
||||
|
||||
// send deposit response to maker and taker
|
||||
sendDepositResponse(trade.getMaker().getNodeAddress(), trade.getMaker().getPubKeyRing(), response);
|
||||
sendDepositResponse(trade.getTaker().getNodeAddress(), trade.getTaker().getPubKeyRing(), response);
|
||||
}
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendDepositResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, DepositResponse response) {
|
||||
log.info("Sending deposit response to trader={}; offerId={}", nodeAddress, trade.getId());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), nodeAddress, trade.getId());
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.OfferDirection;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitTradeRequest;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Arbitrator verifies reserve tx from maker or taker.
|
||||
*
|
||||
* The maker reserve tx is only verified here if this arbitrator is not
|
||||
* the original offer signer and thus does not have the original reserve tx.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ArbitratorProcessReserveTx extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public ArbitratorProcessReserveTx(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
Offer offer = trade.getOffer();
|
||||
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
||||
boolean isFromTaker = request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress());
|
||||
boolean isFromBuyer = isFromTaker ? offer.getDirection() == OfferDirection.SELL : offer.getDirection() == OfferDirection.BUY;
|
||||
|
||||
// TODO (woodser): if signer online, should never be called by maker
|
||||
|
||||
// process reserve tx with expected values
|
||||
BigInteger tradeFee = isFromTaker ? trade.getTakerFee() : trade.getMakerFee();
|
||||
BigInteger sendAmount = isFromBuyer ? BigInteger.valueOf(0) : offer.getAmount();
|
||||
BigInteger securityDeposit = isFromBuyer ? offer.getBuyerSecurityDeposit() : offer.getSellerSecurityDeposit();
|
||||
try {
|
||||
trade.getXmrWalletService().verifyTradeTx(
|
||||
tradeFee,
|
||||
sendAmount,
|
||||
securityDeposit,
|
||||
request.getPayoutAddress(),
|
||||
request.getReserveTxHash(),
|
||||
request.getReserveTxHex(),
|
||||
request.getReserveTxKey(),
|
||||
null);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Error processing reserve tx from " + (isFromTaker ? "taker " : "maker ") + request.getSenderNodeAddress() + ", offerId=" + offer.getId() + ": " + e.getMessage());
|
||||
}
|
||||
|
||||
// save reserve tx to model
|
||||
TradePeer trader = isFromTaker ? processModel.getTaker() : processModel.getMaker();
|
||||
trader.setReserveTxHash(request.getReserveTxHash());
|
||||
trader.setReserveTxHex(request.getReserveTxHex());
|
||||
trader.setReserveTxKey(request.getReserveTxKey());
|
||||
|
||||
// persist trade
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitMultisigRequest;
|
||||
import haveno.core.trade.messages.InitTradeRequest;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.MoneroWallet;
|
||||
|
||||
/**
|
||||
* Arbitrator sends InitTradeRequest to maker after receiving InitTradeRequest
|
||||
* from taker and verifying taker reserve tx.
|
||||
*
|
||||
* Arbitrator sends InitMultisigRequests after the maker acks.
|
||||
*/
|
||||
@Slf4j
|
||||
public class ArbitratorSendInitTradeOrMultisigRequests extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ArbitratorSendInitTradeOrMultisigRequests(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
||||
|
||||
// handle request from taker
|
||||
if (request.getSenderNodeAddress().equals(trade.getTaker().getNodeAddress())) {
|
||||
|
||||
// create request to initialize trade with maker
|
||||
InitTradeRequest makerRequest = new InitTradeRequest(
|
||||
processModel.getOfferId(),
|
||||
request.getSenderNodeAddress(),
|
||||
request.getPubKeyRing(),
|
||||
trade.getAmount().longValueExact(),
|
||||
trade.getPrice().getValue(),
|
||||
trade.getTakerFee().longValueExact(),
|
||||
request.getAccountId(),
|
||||
request.getPaymentAccountId(),
|
||||
request.getPaymentMethodId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
request.getAccountAgeWitnessSignatureOfOfferId(),
|
||||
new Date().getTime(),
|
||||
trade.getMaker().getNodeAddress(),
|
||||
trade.getTaker().getNodeAddress(),
|
||||
trade.getArbitrator().getNodeAddress(),
|
||||
null,
|
||||
null, // do not include taker's reserve tx
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
|
||||
// send request to maker
|
||||
log.info("Send {} with offerId {} and uid {} to maker {}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid(), trade.getMaker().getNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getMaker().getNodeAddress(), // TODO (woodser): maker's address might be different from original owner address if they disconnect and reconnect, need to validate and update address when requests received
|
||||
trade.getMaker().getPubKeyRing(),
|
||||
makerRequest,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at maker: offerId={}; uid={}", makerRequest.getClass().getSimpleName(), makerRequest.getTradeId(), makerRequest.getUid());
|
||||
complete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", makerRequest.getClass().getSimpleName(), makerRequest.getUid(), trade.getArbitrator().getNodeAddress(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + makerRequest + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// handle request from maker
|
||||
else if (request.getSenderNodeAddress().equals(trade.getMaker().getNodeAddress())) {
|
||||
sendInitMultisigRequests();
|
||||
complete(); // TODO: wait for InitMultisigRequest arrivals?
|
||||
} else {
|
||||
throw new RuntimeException("Request is not from maker or taker");
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInitMultisigRequests() {
|
||||
|
||||
// ensure arbitrator has maker's reserve tx
|
||||
if (processModel.getMaker().getReserveTxHash() == null) {
|
||||
throw new RuntimeException("Arbitrator does not have maker's reserve tx after initializing trade");
|
||||
}
|
||||
|
||||
// create wallet for multisig
|
||||
MoneroWallet multisigWallet = trade.createWallet();
|
||||
|
||||
// prepare multisig
|
||||
String preparedHex = multisigWallet.prepareMultisig();
|
||||
trade.getSelf().setPreparedMultisigHex(preparedHex);
|
||||
|
||||
// create message to initialize multisig
|
||||
InitMultisigRequest initMultisigRequest = new InitMultisigRequest(
|
||||
processModel.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
preparedHex,
|
||||
null,
|
||||
null);
|
||||
|
||||
// send request to maker
|
||||
log.info("Send {} with offerId {} and uid {} to maker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid(), trade.getMaker().getNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getMaker().getNodeAddress(),
|
||||
trade.getMaker().getPubKeyRing(),
|
||||
initMultisigRequest,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at maker: offerId={}; uid={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid());
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getMaker().getNodeAddress(), errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// send request to taker
|
||||
log.info("Send {} with offerId {} and uid {} to taker {}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid(), trade.getTaker().getNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getTaker().getNodeAddress(),
|
||||
trade.getTaker().getPubKeyRing(),
|
||||
initMultisigRequest,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at taker: offerId={}; uid={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getTradeId(), initMultisigRequest.getUid());
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", initMultisigRequest.getClass().getSimpleName(), initMultisigRequest.getUid(), trade.getTaker().getNodeAddress(), errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import java.math.BigInteger;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroAccount;
|
||||
import monero.wallet.model.MoneroSubaddress;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
public class BuyerPreparePaymentSentMessage extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public BuyerPreparePaymentSentMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if already created
|
||||
if (processModel.getPaymentSentMessage() != null) {
|
||||
log.warn("Skipping preparation of payment sent message since it's already created for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// validate state
|
||||
Preconditions.checkNotNull(trade.getSeller().getPaymentAccountPayload(), "Seller's payment account payload is null");
|
||||
Preconditions.checkNotNull(trade.getAmount(), "trade.getTradeAmount() must not be null");
|
||||
Preconditions.checkNotNull(trade.getMakerDepositTx(), "trade.getMakerDepositTx() must not be null");
|
||||
Preconditions.checkNotNull(trade.getTakerDepositTx(), "trade.getTakerDepositTx() must not be null");
|
||||
checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
|
||||
// create payout tx if we have seller's updated multisig hex
|
||||
if (trade.getSeller().getUpdatedMultisigHex() != null) {
|
||||
|
||||
// create payout tx
|
||||
log.info("Buyer creating unsigned payout tx");
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
trade.setPayoutTx(payoutTx);
|
||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO (woodser): move these to gen utils
|
||||
|
||||
/**
|
||||
* Generic parameterized pair.
|
||||
*
|
||||
* @author woodser
|
||||
*
|
||||
* @param <F> the type of the first element
|
||||
* @param <S> the type of the second element
|
||||
*/
|
||||
public static class Pair<F, S> {
|
||||
|
||||
private F first;
|
||||
private S second;
|
||||
|
||||
public Pair(F first, S second) {
|
||||
super();
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public F getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public void setFirst(F first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public S getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public void setSecond(S second) {
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
|
||||
public static void printBalances(MoneroWallet wallet) {
|
||||
|
||||
// collect info about subaddresses
|
||||
List<Pair<String, List<Object>>> pairs = new ArrayList<Pair<String, List<Object>>>();
|
||||
//if (wallet == null) wallet = TestUtils.getWalletJni();
|
||||
BigInteger balance = wallet.getBalance();
|
||||
BigInteger unlockedBalance = wallet.getUnlockedBalance();
|
||||
List<MoneroAccount> accounts = wallet.getAccounts(true);
|
||||
System.out.println("Wallet balance: " + balance);
|
||||
System.out.println("Wallet unlocked balance: " + unlockedBalance);
|
||||
for (MoneroAccount account : accounts) {
|
||||
add(pairs, "ACCOUNT", account.getIndex());
|
||||
add(pairs, "SUBADDRESS", "");
|
||||
add(pairs, "LABEL", "");
|
||||
add(pairs, "ADDRESS", "");
|
||||
add(pairs, "BALANCE", account.getBalance());
|
||||
add(pairs, "UNLOCKED", account.getUnlockedBalance());
|
||||
for (MoneroSubaddress subaddress : account.getSubaddresses()) {
|
||||
add(pairs, "ACCOUNT", account.getIndex());
|
||||
add(pairs, "SUBADDRESS", subaddress.getIndex());
|
||||
add(pairs, "LABEL", subaddress.getLabel());
|
||||
add(pairs, "ADDRESS", subaddress.getAddress());
|
||||
add(pairs, "BALANCE", subaddress.getBalance());
|
||||
add(pairs, "UNLOCKED", subaddress.getUnlockedBalance());
|
||||
}
|
||||
}
|
||||
|
||||
// convert info to csv
|
||||
Integer length = null;
|
||||
for (Pair<String, List<Object>> pair : pairs) {
|
||||
if (length == null) length = pair.getSecond().size();
|
||||
}
|
||||
|
||||
System.out.println(pairsToCsv(pairs));
|
||||
}
|
||||
|
||||
private static void add(List<Pair<String, List<Object>>> pairs, String header, Object value) {
|
||||
if (value == null) value = "";
|
||||
Pair<String, List<Object>> pair = null;
|
||||
for (Pair<String, List<Object>> aPair : pairs) {
|
||||
if (aPair.getFirst().equals(header)) {
|
||||
pair = aPair;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (pair == null) {
|
||||
List<Object> vals = new ArrayList<Object>();
|
||||
pair = new Pair<String, List<Object>>(header, vals);
|
||||
pairs.add(pair);
|
||||
}
|
||||
pair.getSecond().add(value);
|
||||
}
|
||||
|
||||
private static String pairsToCsv(List<Pair<String, List<Object>>> pairs) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < pairs.size(); i++) {
|
||||
sb.append(pairs.get(i).getFirst());
|
||||
if (i < pairs.size() - 1) sb.append(',');
|
||||
else sb.append('\n');
|
||||
}
|
||||
for (int i = 0; i < pairs.get(0).getSecond().size(); i++) {
|
||||
for (int j = 0; j < pairs.size(); j++) {
|
||||
sb.append(pairs.get(j).getSecond().get(i));
|
||||
if (j < pairs.size() - 1) sb.append(',');
|
||||
else sb.append('\n');
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.common.Timer;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.network.MessageState;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentSentMessage;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.util.JsonUtil;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* We send the seller the BuyerSendPaymentSentMessage.
|
||||
* We wait to receive a ACK message back and resend the message
|
||||
* in case that does not happen in 10 minutes or if the message was stored in mailbox or failed. We keep repeating that
|
||||
* with doubling the interval each time and until the MAX_RESEND_ATTEMPTS is reached.
|
||||
* If never successful we give up and complete. It might be a valid case that the peer was not online for an extended
|
||||
* time but we can be very sure that our message was stored as mailbox message in the network and one the peer goes
|
||||
* online he will process it.
|
||||
*/
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
|
||||
private ChangeListener<MessageState> listener;
|
||||
private Timer timer;
|
||||
|
||||
public BuyerSendPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected abstract NodeAddress getReceiverNodeAddress();
|
||||
|
||||
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
} finally {
|
||||
cleanup();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
if (processModel.getPaymentSentMessage() == null) {
|
||||
|
||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||
// peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox
|
||||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||
// other data stays the same when we re-send the message at any time later.
|
||||
String deterministicId = HavenoUtils.getDeterministicId(trade, PaymentSentMessage.class, getReceiverNodeAddress());
|
||||
|
||||
// create payment sent message
|
||||
PaymentSentMessage message = new PaymentSentMessage(
|
||||
tradeId,
|
||||
processModel.getMyNodeAddress(),
|
||||
trade.getCounterCurrencyTxId(),
|
||||
trade.getCounterCurrencyExtraData(),
|
||||
deterministicId,
|
||||
trade.getPayoutTxHex(),
|
||||
trade.getSelf().getUpdatedMultisigHex(),
|
||||
trade.getSelf().getPaymentAccountKey(),
|
||||
trade.getTradePeer().getAccountAgeWitness()
|
||||
);
|
||||
|
||||
// sign message
|
||||
try {
|
||||
String messageAsJson = JsonUtil.objectToJson(message);
|
||||
byte[] sig = Sig.sign(processModel.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), messageAsJson.getBytes(Charsets.UTF_8));
|
||||
message.setBuyerSignature(sig);
|
||||
processModel.setPaymentSentMessage(message);
|
||||
trade.requestPersistence();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException (e);
|
||||
}
|
||||
}
|
||||
return processModel.getPaymentSentMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
if (trade.getState().ordinal() < Trade.State.BUYER_SENT_PAYMENT_SENT_MSG.ordinal()) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
}
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
// TODO: schedule repeat sending like haveno?
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
private void cleanup() {
|
||||
if (timer != null) {
|
||||
timer.stop();
|
||||
}
|
||||
if (listener != null) {
|
||||
processModel.getPaymentSentMessageStateProperty().removeListener(listener);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public class BuyerSendPaymentSentMessageToArbitrator extends BuyerSendPaymentSentMessage {
|
||||
|
||||
public BuyerSendPaymentSentMessageToArbitrator(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getArbitrator().getNodeAddress();
|
||||
}
|
||||
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getArbitrator().getPubKeyRing();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
complete(); // don't wait for message to arbitrator
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// state only updated on seller message
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.TradeMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public class BuyerSendPaymentSentMessageToSeller extends BuyerSendPaymentSentMessage {
|
||||
|
||||
public BuyerSendPaymentSentMessageToSeller(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getSeller().getNodeAddress();
|
||||
}
|
||||
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getSeller().getPubKeyRing();
|
||||
}
|
||||
|
||||
// continue execution on fault so payment sent message is sent to arbitrator
|
||||
@Override
|
||||
protected void onFault(String errorMessage, TradeMessage message) {
|
||||
setStateFault();
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.btc.model.XmrAddressEntry;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitTradeRequest;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static haveno.core.util.Validator.checkTradeId;
|
||||
|
||||
@Slf4j
|
||||
public class MakerSendInitTradeRequest extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public MakerSendInitTradeRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// verify trade
|
||||
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // arbitrator's InitTradeRequest to maker
|
||||
checkNotNull(makerRequest);
|
||||
checkTradeId(processModel.getOfferId(), makerRequest);
|
||||
|
||||
// create request to arbitrator
|
||||
Offer offer = processModel.getOffer();
|
||||
InitTradeRequest arbitratorRequest = new InitTradeRequest(
|
||||
offer.getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
offer.getAmount().longValueExact(),
|
||||
trade.getPrice().getValue(),
|
||||
offer.getMakerFee().longValueExact(),
|
||||
trade.getProcessModel().getAccountId(),
|
||||
offer.getMakerPaymentAccountId(),
|
||||
offer.getOfferPayload().getPaymentMethodId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
null,
|
||||
makerRequest.getCurrentDate(),
|
||||
trade.getMaker().getNodeAddress(),
|
||||
trade.getTaker().getNodeAddress(),
|
||||
trade.getArbitrator().getNodeAddress(),
|
||||
trade.getSelf().getReserveTxHash(),
|
||||
trade.getSelf().getReserveTxHex(),
|
||||
trade.getSelf().getReserveTxKey(),
|
||||
model.getXmrWalletService().getOrCreateAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString(),
|
||||
null);
|
||||
|
||||
// send request to arbitrator
|
||||
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
trade.getArbitrator().getNodeAddress(),
|
||||
trade.getArbitrator().getPubKeyRing(),
|
||||
arbitratorRequest,
|
||||
new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId());
|
||||
complete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.warn("Failed to send {} to arbitrator, error={}.", InitTradeRequest.class.getSimpleName(), errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.config.Config;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.btc.wallet.Restrictions;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class MakerSetLockTime extends TradeTask {
|
||||
public MakerSetLockTime(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// 10 days for altcoins, 20 days for other payment methods
|
||||
// For regtest dev environment we use 5 blocks
|
||||
int delay = Config.baseCurrencyNetwork().isTestnet() ?
|
||||
5 :
|
||||
Restrictions.getLockTime(processModel.getOffer().getPaymentMethod().isBlockchain());
|
||||
|
||||
long lockTime = processModel.getBtcWalletService().getBestChainHeight() + delay;
|
||||
log.info("lockTime={}, delay={}", lockTime, delay);
|
||||
trade.setLockTime(lockTime);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.btc.model.XmrAddressEntry;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.Trade.State;
|
||||
import haveno.core.trade.messages.SignContractRequest;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
// TODO (woodser): separate classes for deposit tx creation and contract request, or combine into ProcessInitMultisigRequest
|
||||
@Slf4j
|
||||
public class MaybeSendSignContractRequest extends TradeTask {
|
||||
|
||||
private boolean ack1 = false; // TODO (woodser) these represent onArrived(), not the ack
|
||||
private boolean ack2 = false;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public MaybeSendSignContractRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// skip if arbitrator
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if multisig wallet not complete
|
||||
if (processModel.getMultisigAddress() == null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if deposit tx already created
|
||||
if (processModel.getDepositTxXmr() != null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// create deposit tx and freeze inputs
|
||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
||||
|
||||
// collect reserved key images
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : depositTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save process state
|
||||
processModel.setDepositTxXmr(depositTx); // TODO: redundant with trade.getSelf().setDepositTx(), remove?
|
||||
trade.getSelf().setDepositTx(depositTx);
|
||||
trade.getSelf().setDepositTxHash(depositTx.getHash());
|
||||
trade.getSelf().setReserveTxKeyImages(reservedKeyImages);
|
||||
trade.getSelf().setPayoutAddressString(trade.getXmrWalletService().getAddressEntry(processModel.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString()); // TODO (woodser): allow custom payout address?
|
||||
trade.getSelf().setPaymentAccountPayload(trade.getProcessModel().getPaymentAccountPayload(trade.getSelf().getPaymentAccountId()));
|
||||
|
||||
// maker signs deposit hash nonce to avoid challenge protocol
|
||||
byte[] sig = null;
|
||||
if (trade instanceof MakerTrade) {
|
||||
sig = Sig.sign(processModel.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), depositTx.getHash().getBytes(Charsets.UTF_8));
|
||||
}
|
||||
|
||||
// create request for peer and arbitrator to sign contract
|
||||
SignContractRequest request = new SignContractRequest(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
trade.getProcessModel().getAccountId(),
|
||||
trade.getSelf().getPaymentAccountPayload().getHash(),
|
||||
trade.getSelf().getPayoutAddressString(),
|
||||
depositTx.getHash(),
|
||||
sig);
|
||||
|
||||
// send request to trading peer
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradePeer().getNodeAddress(), trade.getTradePeer().getPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId());
|
||||
ack1 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradePeer().getNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send request to arbitrator
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeAux() {
|
||||
trade.setState(State.CONTRACT_SIGNATURE_REQUESTED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessDepositResponse extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessDepositResponse(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// throw if error
|
||||
DepositResponse message = (DepositResponse) processModel.getTradeMessage();
|
||||
if (message.getErrorMessage() != null) {
|
||||
throw new RuntimeException(message.getErrorMessage());
|
||||
}
|
||||
|
||||
// set success state
|
||||
trade.setStateIfValidTransitionTo(Trade.State.ARBITRATOR_PUBLISHED_DEPOSIT_TXS);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
trade.setStateIfValidTransitionTo(Trade.State.PUBLISH_DEPOSIT_TX_REQUEST_FAILED);
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.util.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessDepositsConfirmedMessage extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessDepositsConfirmedMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// get peer
|
||||
DepositsConfirmedMessage request = (DepositsConfirmedMessage) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
TradePeer sender = trade.getTradePeer(request.getPubKeyRing());
|
||||
if (sender == null) throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
|
||||
|
||||
// update peer node address
|
||||
sender.setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
if (sender.getNodeAddress().equals(trade.getBuyer().getNodeAddress()) && sender != trade.getBuyer()) trade.getBuyer().setNodeAddress(null); // tests can reuse addresses
|
||||
if (sender.getNodeAddress().equals(trade.getSeller().getNodeAddress()) && sender != trade.getSeller()) trade.getSeller().setNodeAddress(null);
|
||||
if (sender.getNodeAddress().equals(trade.getArbitrator().getNodeAddress()) && sender != trade.getArbitrator()) trade.getArbitrator().setNodeAddress(null);
|
||||
|
||||
// update multisig hex
|
||||
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
||||
trade.importMultisigHex();
|
||||
|
||||
// decrypt seller payment account payload if key given
|
||||
if (request.getSellerPaymentAccountKey() != null && trade.getTradePeer().getPaymentAccountPayload() == null) {
|
||||
log.info(trade.getClass().getSimpleName() + " decrypting using seller payment account key");
|
||||
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
||||
}
|
||||
|
||||
// persist and complete
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.btc.wallet.XmrWalletService;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.TakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitMultisigRequest;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static haveno.core.util.Validator.checkTradeId;
|
||||
|
||||
import monero.wallet.MoneroWallet;
|
||||
import monero.wallet.model.MoneroMultisigInitResult;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessInitMultisigRequest extends TradeTask {
|
||||
|
||||
private boolean ack1 = false;
|
||||
private boolean ack2 = false;
|
||||
MoneroWallet multisigWallet;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessInitMultisigRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
InitMultisigRequest request = (InitMultisigRequest) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
XmrWalletService xmrWalletService = processModel.getProvider().getXmrWalletService();
|
||||
|
||||
// get peer multisig participant
|
||||
TradePeer multisigParticipant = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
// reconcile peer's established multisig hex with message
|
||||
if (multisigParticipant.getPreparedMultisigHex() == null) multisigParticipant.setPreparedMultisigHex(request.getPreparedMultisigHex());
|
||||
else if (request.getPreparedMultisigHex() != null && !multisigParticipant.getPreparedMultisigHex().equals(request.getPreparedMultisigHex())) throw new RuntimeException("Message's prepared multisig differs from previous messages, previous: " + multisigParticipant.getPreparedMultisigHex() + ", message: " + request.getPreparedMultisigHex());
|
||||
if (multisigParticipant.getMadeMultisigHex() == null) multisigParticipant.setMadeMultisigHex(request.getMadeMultisigHex());
|
||||
else if (request.getMadeMultisigHex() != null && !multisigParticipant.getMadeMultisigHex().equals(request.getMadeMultisigHex())) throw new RuntimeException("Message's made multisig differs from previous messages: " + request.getMadeMultisigHex() + " versus " + multisigParticipant.getMadeMultisigHex());
|
||||
if (multisigParticipant.getExchangedMultisigHex() == null) multisigParticipant.setExchangedMultisigHex(request.getExchangedMultisigHex());
|
||||
else if (request.getExchangedMultisigHex() != null && !multisigParticipant.getExchangedMultisigHex().equals(request.getExchangedMultisigHex())) throw new RuntimeException("Message's exchanged multisig differs from previous messages: " + request.getExchangedMultisigHex() + " versus " + multisigParticipant.getExchangedMultisigHex());
|
||||
|
||||
// prepare multisig if applicable
|
||||
boolean updateParticipants = false;
|
||||
if (trade.getSelf().getPreparedMultisigHex() == null) {
|
||||
log.info("Preparing multisig wallet for trade {}", trade.getId());
|
||||
multisigWallet = trade.createWallet();
|
||||
trade.getSelf().setPreparedMultisigHex(multisigWallet.prepareMultisig());
|
||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_PREPARED);
|
||||
updateParticipants = true;
|
||||
} else if (processModel.getMultisigAddress() == null) {
|
||||
multisigWallet = trade.getWallet();
|
||||
}
|
||||
|
||||
// make multisig if applicable
|
||||
TradePeer[] peers = getMultisigPeers();
|
||||
if (trade.getSelf().getMadeMultisigHex() == null && peers[0].getPreparedMultisigHex() != null && peers[1].getPreparedMultisigHex() != null) {
|
||||
log.info("Making multisig wallet for trade {}", trade.getId());
|
||||
String multisigHex = multisigWallet.makeMultisig(Arrays.asList(peers[0].getPreparedMultisigHex(), peers[1].getPreparedMultisigHex()), 2, xmrWalletService.getWalletPassword()); // TODO (woodser): xmrWalletService.makeMultisig(tradeId, multisigHexes, threshold)?
|
||||
trade.getSelf().setMadeMultisigHex(multisigHex);
|
||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_MADE);
|
||||
updateParticipants = true;
|
||||
}
|
||||
|
||||
// import made multisig keys if applicable
|
||||
if (trade.getSelf().getExchangedMultisigHex() == null && peers[0].getMadeMultisigHex() != null && peers[1].getMadeMultisigHex() != null) {
|
||||
log.info("Importing made multisig hex for trade {}", trade.getId());
|
||||
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getMadeMultisigHex(), peers[1].getMadeMultisigHex()), xmrWalletService.getWalletPassword());
|
||||
trade.getSelf().setExchangedMultisigHex(result.getMultisigHex());
|
||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_EXCHANGED);
|
||||
updateParticipants = true;
|
||||
}
|
||||
|
||||
// import exchanged multisig keys if applicable
|
||||
if (processModel.getMultisigAddress() == null && peers[0].getExchangedMultisigHex() != null && peers[1].getExchangedMultisigHex() != null) {
|
||||
log.info("Importing exchanged multisig hex for trade {}", trade.getId());
|
||||
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword());
|
||||
processModel.setMultisigAddress(result.getAddress());
|
||||
trade.saveWallet(); // save multisig wallet on completion
|
||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
|
||||
}
|
||||
|
||||
// update multisig participants if new state to communicate
|
||||
if (updateParticipants) {
|
||||
|
||||
// get destination addresses and pub key rings // TODO: better way, use getMultisigPeers()
|
||||
NodeAddress peer1Address;
|
||||
PubKeyRing peer1PubKeyRing;
|
||||
NodeAddress peer2Address;
|
||||
PubKeyRing peer2PubKeyRing;
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
peer1Address = trade.getTaker().getNodeAddress();
|
||||
peer1PubKeyRing = trade.getTaker().getPubKeyRing();
|
||||
peer2Address = trade.getMaker().getNodeAddress();
|
||||
peer2PubKeyRing = trade.getMaker().getPubKeyRing();
|
||||
} else if (trade instanceof MakerTrade) {
|
||||
peer1Address = trade.getTaker().getNodeAddress();
|
||||
peer1PubKeyRing = trade.getTaker().getPubKeyRing();
|
||||
peer2Address = trade.getArbitrator().getNodeAddress();
|
||||
peer2PubKeyRing = trade.getArbitrator().getPubKeyRing();
|
||||
} else {
|
||||
peer1Address = trade.getMaker().getNodeAddress();
|
||||
peer1PubKeyRing = trade.getMaker().getPubKeyRing();
|
||||
peer2Address = trade.getArbitrator().getNodeAddress();
|
||||
peer2PubKeyRing = trade.getArbitrator().getPubKeyRing();
|
||||
}
|
||||
|
||||
if (peer1Address == null) throw new RuntimeException("Peer1 address is null");
|
||||
if (peer1PubKeyRing == null) throw new RuntimeException("Peer1 pub key ring is null");
|
||||
if (peer2Address == null) throw new RuntimeException("Peer2 address is null");
|
||||
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null");
|
||||
|
||||
// send to peer 1
|
||||
sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer1Address, request.getTradeId(), request.getUid());
|
||||
ack1 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send to peer 2
|
||||
sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer2Address, request.getTradeId(), request.getUid());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer2Address, errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
completeAux();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private TradePeer[] getMultisigPeers() {
|
||||
TradePeer[] peers = new TradePeer[2];
|
||||
if (trade instanceof TakerTrade) {
|
||||
peers[0] = processModel.getArbitrator();
|
||||
peers[1] = processModel.getMaker();
|
||||
} else if (trade instanceof MakerTrade) {
|
||||
peers[1] = processModel.getTaker();
|
||||
peers[0] = processModel.getArbitrator();
|
||||
} else {
|
||||
peers[0] = processModel.getTaker();
|
||||
peers[1] = processModel.getMaker();
|
||||
}
|
||||
return peers;
|
||||
}
|
||||
|
||||
private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) {
|
||||
|
||||
// create multisig message with current multisig hex
|
||||
InitMultisigRequest request = new InitMultisigRequest(
|
||||
processModel.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
trade.getSelf().getPreparedMultisigHex(),
|
||||
trade.getSelf().getMadeMultisigHex(),
|
||||
trade.getSelf().getExchangedMultisigHex());
|
||||
|
||||
log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient);
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener);
|
||||
}
|
||||
|
||||
private void completeAux() {
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.exceptions.TradePriceOutOfToleranceException;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitTradeRequest;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static haveno.core.util.Validator.checkTradeId;
|
||||
import static haveno.core.util.Validator.nonEmptyStringOf;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Date;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessInitTradeRequest extends TradeTask {
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessInitTradeRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
// TODO (woodser): synchronize access to setting trade state in case of concurrent requests
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
Offer offer = checkNotNull(trade.getOffer(), "Offer must not be null");
|
||||
InitTradeRequest request = (InitTradeRequest) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
checkTradeId(processModel.getOfferId(), request);
|
||||
|
||||
// handle request as arbitrator
|
||||
TradePeer multisigParticipant;
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
trade.getMaker().setPubKeyRing((trade.getOffer().getPubKeyRing()));
|
||||
trade.getArbitrator().setPubKeyRing(processModel.getPubKeyRing()); // TODO (woodser): why duplicating field in process model
|
||||
|
||||
// handle request from taker
|
||||
if (request.getSenderNodeAddress().equals(request.getTakerNodeAddress())) {
|
||||
multisigParticipant = processModel.getTaker();
|
||||
if (!trade.getTaker().getNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree");
|
||||
if (trade.getTaker().getPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
|
||||
trade.getTaker().setPubKeyRing(request.getPubKeyRing());
|
||||
if (!HavenoUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
|
||||
|
||||
// check trade price
|
||||
try {
|
||||
long tradePrice = request.getTradePrice();
|
||||
offer.verifyTakersTradePrice(tradePrice);
|
||||
trade.setPrice(tradePrice);
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
failed(e.getMessage());
|
||||
} catch (Throwable e2) {
|
||||
failed(e2);
|
||||
}
|
||||
}
|
||||
|
||||
// handle request from maker
|
||||
else if (request.getSenderNodeAddress().equals(request.getMakerNodeAddress())) {
|
||||
multisigParticipant = processModel.getMaker();
|
||||
if (!trade.getMaker().getNodeAddress().equals(request.getMakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): test when maker and taker do not agree, use proper handling, uninitialize trade for other takers
|
||||
if (trade.getMaker().getPubKeyRing() == null) trade.getMaker().setPubKeyRing(request.getPubKeyRing());
|
||||
else if (!trade.getMaker().getPubKeyRing().equals(request.getPubKeyRing())) throw new RuntimeException("Init trade requests from maker and taker do not agree"); // TODO (woodser): proper handling
|
||||
trade.getMaker().setPubKeyRing(request.getPubKeyRing());
|
||||
if (trade.getPrice().getValue() != request.getTradePrice()) throw new RuntimeException("Maker and taker price do not agree");
|
||||
} else {
|
||||
throw new RuntimeException("Sender is not trade's maker or taker");
|
||||
}
|
||||
}
|
||||
|
||||
// handle request as maker
|
||||
else if (trade instanceof MakerTrade) {
|
||||
multisigParticipant = processModel.getTaker();
|
||||
trade.getTaker().setNodeAddress(request.getSenderNodeAddress()); // arbitrator sends maker InitTradeRequest with taker's node address and pub key ring
|
||||
trade.getTaker().setPubKeyRing(request.getPubKeyRing());
|
||||
|
||||
// check trade price
|
||||
try {
|
||||
long tradePrice = request.getTradePrice();
|
||||
offer.verifyTakersTradePrice(tradePrice);
|
||||
trade.setPrice(tradePrice);
|
||||
} catch (TradePriceOutOfToleranceException e) {
|
||||
failed(e.getMessage());
|
||||
} catch (Throwable e2) {
|
||||
failed(e2);
|
||||
}
|
||||
}
|
||||
|
||||
// handle invalid trade type
|
||||
else {
|
||||
throw new RuntimeException("Invalid trade type to process init trade request: " + trade.getClass().getName());
|
||||
}
|
||||
|
||||
// set trading peer info
|
||||
if (multisigParticipant.getPaymentAccountId() == null) multisigParticipant.setPaymentAccountId(request.getPaymentAccountId());
|
||||
else if (multisigParticipant.getPaymentAccountId() != request.getPaymentAccountId()) throw new RuntimeException("Payment account id is different from previous");
|
||||
multisigParticipant.setPubKeyRing(checkNotNull(request.getPubKeyRing()));
|
||||
multisigParticipant.setAccountId(nonEmptyStringOf(request.getAccountId()));
|
||||
multisigParticipant.setPaymentMethodId(nonEmptyStringOf(request.getPaymentMethodId()));
|
||||
multisigParticipant.setAccountAgeWitnessNonce(trade.getId().getBytes(Charsets.UTF_8));
|
||||
multisigParticipant.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfOfferId());
|
||||
multisigParticipant.setCurrentDate(request.getCurrentDate());
|
||||
|
||||
// check peer's current date
|
||||
processModel.getAccountAgeWitnessService().verifyPeersCurrentDate(new Date(multisigParticipant.getCurrentDate()));
|
||||
|
||||
// check trade amount
|
||||
checkArgument(request.getTradeAmount() > 0);
|
||||
trade.setAmount(BigInteger.valueOf(request.getTradeAmount()));
|
||||
|
||||
// persist trade
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import common.utils.GenUtils;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.sign.SignedWitness;
|
||||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.BuyerTrade;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
import haveno.core.util.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||
public ProcessPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
log.debug("current trade state " + trade.getState());
|
||||
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
||||
checkNotNull(message);
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkArgument(message.getUnsignedPayoutTxHex() != null || message.getSignedPayoutTxHex() != null, "No payout tx hex provided");
|
||||
|
||||
// verify signature of payment received message
|
||||
HavenoUtils.verifyPaymentReceivedMessage(trade, message);
|
||||
|
||||
// save message for reprocessing
|
||||
processModel.setPaymentReceivedMessage(message);
|
||||
trade.requestPersistence();
|
||||
|
||||
// set state
|
||||
trade.getSeller().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getPaymentSentMessage().getUpdatedMultisigHex());
|
||||
trade.getBuyer().setAccountAgeWitness(message.getBuyerAccountAgeWitness());
|
||||
|
||||
// update to the latest peer address of our peer if message is correct
|
||||
trade.getSeller().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
if (trade.getSeller().getNodeAddress().equals(trade.getBuyer().getNodeAddress())) trade.getBuyer().setNodeAddress(null); // tests can reuse addresses
|
||||
|
||||
// close open disputes
|
||||
if (trade.getDisputeState().ordinal() >= Trade.DisputeState.DISPUTE_REQUESTED.ordinal()) {
|
||||
trade.advanceDisputeState(Trade.DisputeState.DISPUTE_CLOSED);
|
||||
for (Dispute dispute : trade.getDisputes()) {
|
||||
dispute.setIsClosed();
|
||||
}
|
||||
}
|
||||
|
||||
// process payout tx unless already unlocked
|
||||
if (!trade.isPayoutUnlocked()) processPayoutTx(message);
|
||||
|
||||
SignedWitness signedWitness = message.getBuyerSignedWitness();
|
||||
if (signedWitness != null && trade instanceof BuyerTrade) {
|
||||
// We received the signedWitness from the seller and publish the data to the network.
|
||||
// The signer has published it as well but we prefer to re-do it on our side as well to achieve higher
|
||||
// resilience.
|
||||
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
|
||||
}
|
||||
|
||||
// complete
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG); // arbitrator auto completes when payout published
|
||||
trade.requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
|
||||
// do not reprocess illegal argument
|
||||
if (t instanceof IllegalArgumentException) {
|
||||
processModel.setPaymentReceivedMessage(null); // do not reprocess
|
||||
trade.requestPersistence();
|
||||
}
|
||||
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void processPayoutTx(PaymentReceivedMessage message) {
|
||||
|
||||
// update wallet
|
||||
trade.importMultisigHex();
|
||||
trade.syncWallet();
|
||||
trade.saveWallet();
|
||||
|
||||
// handle if payout tx not published
|
||||
if (!trade.isPayoutPublished()) {
|
||||
|
||||
// wait to sign and publish payout tx if defer flag set (seller recently saw payout tx arrive at buyer)
|
||||
boolean isSigned = message.getSignedPayoutTxHex() != null;
|
||||
if (trade instanceof ArbitratorTrade && !isSigned && message.isDeferPublishPayout()) {
|
||||
log.info("Deferring signing and publishing payout tx for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
GenUtils.waitFor(Trade.DEFER_PUBLISH_MS);
|
||||
if (!trade.isPayoutUnlocked()) trade.syncWallet();
|
||||
}
|
||||
|
||||
// verify and publish payout tx
|
||||
if (!trade.isPayoutPublished()) {
|
||||
if (isSigned) {
|
||||
log.info("{} {} publishing signed payout tx from seller", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||
} else {
|
||||
try {
|
||||
if (StringUtils.equals(trade.getPayoutTxHex(), trade.getProcessModel().getPaymentSentMessage().getPayoutTxHex())) { // unsigned
|
||||
log.info("{} {} verifying, signing, and publishing seller's payout tx", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.verifyPayoutTx(message.getUnsignedPayoutTxHex(), true, true);
|
||||
} else {
|
||||
log.info("{} {} re-verifying and publishing payout tx", trade.getClass().getSimpleName(), trade.getId());
|
||||
trade.verifyPayoutTx(trade.getPayoutTxHex(), false, true);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
if (trade.isPayoutPublished()) log.info("Payout tx already published for {} {}", trade.getClass().getName(), trade.getId());
|
||||
else throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Payout tx already published for {} {}", trade.getClass().getSimpleName(), trade.getId());
|
||||
if (message.getSignedPayoutTxHex() != null && !trade.isPayoutConfirmed()) trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentSentMessage;
|
||||
import haveno.core.util.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessPaymentSentMessage extends TradeTask {
|
||||
public ProcessPaymentSentMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
PaymentSentMessage message = (PaymentSentMessage) processModel.getTradeMessage();
|
||||
checkNotNull(message);
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
|
||||
// verify signature of payment sent message
|
||||
HavenoUtils.verifyPaymentSentMessage(trade, message);
|
||||
|
||||
// set state
|
||||
processModel.setPaymentSentMessage(message);
|
||||
trade.setPayoutTxHex(message.getPayoutTxHex());
|
||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||
trade.getSeller().setAccountAgeWitness(message.getSellerAccountAgeWitness());
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
|
||||
// if seller, decrypt buyer's payment account payload
|
||||
if (trade.isSeller()) trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||
|
||||
// update latest peer address
|
||||
trade.getBuyer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
// set state
|
||||
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
||||
if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) trade.setCounterCurrencyTxId(counterCurrencyTxId);
|
||||
String counterCurrencyExtraData = message.getCounterCurrencyExtraData();
|
||||
if (counterCurrencyExtraData != null && counterCurrencyExtraData.length() < 100) trade.setCounterCurrencyExtraData(counterCurrencyExtraData);
|
||||
trade.advanceState(trade.isSeller() ? Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG : Trade.State.BUYER_SENT_PAYMENT_SENT_MSG);
|
||||
trade.requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.crypto.Encryption;
|
||||
import haveno.common.crypto.Hash;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.ScryptUtil;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.Contract;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.Trade.State;
|
||||
import haveno.core.trade.messages.SignContractRequest;
|
||||
import haveno.core.trade.messages.SignContractResponse;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.util.JsonUtil;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessSignContractRequest extends TradeTask {
|
||||
|
||||
private boolean ack1 = false;
|
||||
private boolean ack2 = false;
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessSignContractRequest(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// extract fields from request
|
||||
// TODO (woodser): verify request and from maker or taker
|
||||
SignContractRequest request = (SignContractRequest) processModel.getTradeMessage();
|
||||
TradePeer trader = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
trader.setDepositTxHash(request.getDepositTxHash());
|
||||
trader.setAccountId(request.getAccountId());
|
||||
trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
|
||||
trader.setPayoutAddressString(request.getPayoutAddress());
|
||||
|
||||
// maker sends witness signature of deposit tx hash
|
||||
if (trader == trade.getMaker()) {
|
||||
trader.setAccountAgeWitnessNonce(request.getDepositTxHash().getBytes(Charsets.UTF_8));
|
||||
trader.setAccountAgeWitnessSignature(request.getAccountAgeWitnessSignatureOfDepositHash());
|
||||
}
|
||||
|
||||
// sign contract only when both deposit txs hashes known
|
||||
// TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade
|
||||
if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// create and sign contract
|
||||
Contract contract = trade.createContract();
|
||||
String contractAsJson = JsonUtil.objectToJson(contract);
|
||||
String signature = Sig.sign(processModel.getKeyRing().getSignatureKeyPair().getPrivate(), contractAsJson);
|
||||
|
||||
// save contract and signature
|
||||
trade.setContract(contract);
|
||||
trade.setContractAsJson(contractAsJson);
|
||||
trade.setContractHash(Hash.getSha256Hash(checkNotNull(contractAsJson)));
|
||||
trade.getSelf().setContractSignature(signature);
|
||||
|
||||
// traders send encrypted payment account payload
|
||||
byte[] encryptedPaymentAccountPayload = null;
|
||||
if (!trade.isArbitrator()) {
|
||||
|
||||
// generate random key to encrypt payment account payload
|
||||
byte[] decryptionKey = ScryptUtil.getKeyCrypterScrypt().deriveKey(UUID.randomUUID().toString()).getKey();
|
||||
trade.getSelf().setPaymentAccountKey(decryptionKey);
|
||||
|
||||
// encrypt payment account payload
|
||||
byte[] unencrypted = trade.getSelf().getPaymentAccountPayload().toProtoMessage().toByteArray();
|
||||
SecretKey sk = Encryption.getSecretKeyFromBytes(trade.getSelf().getPaymentAccountKey());
|
||||
encryptedPaymentAccountPayload = Encryption.encrypt(unencrypted, sk);
|
||||
}
|
||||
|
||||
// create response with contract signature
|
||||
SignContractResponse response = new SignContractResponse(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
contractAsJson,
|
||||
signature,
|
||||
encryptedPaymentAccountPayload);
|
||||
|
||||
// get response recipients. only arbitrator sends response to both peers
|
||||
NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMaker().getNodeAddress() : trade.getTradePeer().getNodeAddress();
|
||||
PubKeyRing recipient1PubKey = trade instanceof ArbitratorTrade ? trade.getMaker().getPubKeyRing() : trade.getTradePeer().getPubKeyRing();
|
||||
NodeAddress recipient2 = trade instanceof ArbitratorTrade ? trade.getTaker().getNodeAddress() : null;
|
||||
PubKeyRing recipient2PubKey = trade instanceof ArbitratorTrade ? trade.getTaker().getPubKeyRing() : null;
|
||||
|
||||
// send response to recipient 1
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient1, recipient1PubKey, response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient1, trade.getId());
|
||||
ack1 = true;
|
||||
if (ack1 && (recipient2 == null || ack2)) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient1, trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// send response to recipient 2 if applicable
|
||||
if (recipient2 != null) {
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(recipient2, recipient2PubKey, response, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient2, trade.getId());
|
||||
ack2 = true;
|
||||
if (ack1 && ack2) completeAux();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient2, trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeAux() {
|
||||
trade.setState(State.CONTRACT_SIGNED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositRequest;
|
||||
import haveno.core.trade.messages.SignContractResponse;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessSignContractResponse extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ProcessSignContractResponse(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// compare contracts
|
||||
String contractAsJson = trade.getContractAsJson();
|
||||
SignContractResponse response = (SignContractResponse) processModel.getTradeMessage();
|
||||
if (!contractAsJson.equals(response.getContractAsJson())) {
|
||||
trade.getContract().printDiff(response.getContractAsJson());
|
||||
failed("Contracts are not matching");
|
||||
return;
|
||||
}
|
||||
|
||||
// get peer info
|
||||
TradePeer peer = trade.getTradePeer(processModel.getTempTradePeerNodeAddress());
|
||||
PubKeyRing peerPubKeyRing = peer.getPubKeyRing();
|
||||
|
||||
// save peer's encrypted payment account payload
|
||||
peer.setEncryptedPaymentAccountPayload(response.getEncryptedPaymentAccountPayload());
|
||||
if (peer.getEncryptedPaymentAccountPayload() == null) throw new RuntimeException("Peer did not send encrypted payment account payload");
|
||||
|
||||
// verify signature
|
||||
// TODO (woodser): transfer contract for convenient comparison?
|
||||
String signature = response.getContractSignature();
|
||||
if (!Sig.verify(peerPubKeyRing.getSignaturePubKey(), contractAsJson, signature)) throw new RuntimeException("Peer's contract signature is invalid");
|
||||
|
||||
// set peer's signature
|
||||
peer.setContractSignature(signature);
|
||||
|
||||
// send deposit request when all contract signatures received
|
||||
if (processModel.getArbitrator().getContractSignature() != null && processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
|
||||
|
||||
// create request for arbitrator to deposit funds to multisig
|
||||
DepositRequest request = new DepositRequest(
|
||||
trade.getOffer().getId(),
|
||||
UUID.randomUUID().toString(),
|
||||
Version.getP2PMessageVersion(),
|
||||
new Date().getTime(),
|
||||
trade.getSelf().getContractSignature(),
|
||||
processModel.getDepositTxXmr().getFullHex(),
|
||||
processModel.getDepositTxXmr().getKey(),
|
||||
trade.getSelf().getPaymentAccountKey());
|
||||
|
||||
// send request to arbitrator
|
||||
log.info("Sending {} to arbitrator {}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), request.getUid());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing(), request, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived: arbitrator={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), request.getUid());
|
||||
trade.setStateIfValidTransitionTo(Trade.State.SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitrator().getNodeAddress(), trade.getId(), errorMessage);
|
||||
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
|
||||
failed();
|
||||
}
|
||||
});
|
||||
|
||||
// deposit is requested
|
||||
trade.setState(Trade.State.SENT_PUBLISH_DEPOSIT_TX_REQUEST);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
} else {
|
||||
log.info("Waiting for another contract signatures to send deposit request");
|
||||
complete(); // does not yet have needed signatures
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.offer.OfferPayload;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.statistics.TradeStatistics2;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.network.NetworkNode;
|
||||
import haveno.network.p2p.network.TorNetworkNode;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class PublishTradeStatistics extends TradeTask {
|
||||
public PublishTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
checkNotNull(trade.getMakerDepositTx());
|
||||
checkNotNull(trade.getTakerDepositTx());
|
||||
|
||||
Map<String, String> extraDataMap = new HashMap<>();
|
||||
if (processModel.getReferralIdService().getOptionalReferralId().isPresent()) {
|
||||
extraDataMap.put(OfferPayload.REFERRAL_ID, processModel.getReferralIdService().getOptionalReferralId().get());
|
||||
}
|
||||
|
||||
NodeAddress mediatorNodeAddress = checkNotNull(trade.getArbitrator().getNodeAddress());
|
||||
// The first 4 chars are sufficient to identify a mediator.
|
||||
// For testing with regtest/localhost we use the full address as its localhost and would result in
|
||||
// same values for multiple mediators.
|
||||
NetworkNode networkNode = model.getProcessModel().getP2PService().getNetworkNode();
|
||||
String address = networkNode instanceof TorNetworkNode ?
|
||||
mediatorNodeAddress.getFullAddress().substring(0, 4) :
|
||||
mediatorNodeAddress.getFullAddress();
|
||||
extraDataMap.put(TradeStatistics2.ARBITRATOR_ADDRESS, address);
|
||||
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
TradeStatistics2 tradeStatistics = new TradeStatistics2(offer.getOfferPayload(),
|
||||
trade.getPrice(),
|
||||
trade.getAmount(),
|
||||
trade.getDate(),
|
||||
trade.getMaker().getDepositTxHash(),
|
||||
trade.getTaker().getDepositTxHash(),
|
||||
extraDataMap);
|
||||
processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.MakerTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class RemoveOffer extends TradeTask {
|
||||
public RemoveOffer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
if (trade instanceof MakerTrade) {
|
||||
processModel.getOpenOfferManager().closeOpenOffer(checkNotNull(trade.getOffer()));
|
||||
} else {
|
||||
trade.getXmrWalletService().resetAddressEntriesForOpenOffer(trade.getId());
|
||||
}
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.support.dispute.Dispute;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import haveno.core.util.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
public class ResendDisputeClosedMessageWithPayout extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public ResendDisputeClosedMessageWithPayout(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// get peer
|
||||
DepositsConfirmedMessage request = (DepositsConfirmedMessage) processModel.getTradeMessage();
|
||||
checkNotNull(request);
|
||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
||||
TradePeer sender = trade.getTradePeer(request.getPubKeyRing());
|
||||
if (sender == null) throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
|
||||
|
||||
// arbitrator resends DisputeClosedMessage with payout tx when updated multisig info received
|
||||
boolean ticketClosed = false;
|
||||
if (!trade.isPayoutPublished() && trade.isArbitrator()) {
|
||||
List<Dispute> disputes = trade.getDisputes();
|
||||
for (Dispute dispute : disputes) {
|
||||
if (!dispute.isClosed()) continue; // dispute must be closed
|
||||
if (sender.getPubKeyRing().equals(dispute.getTraderPubKeyRing())) {
|
||||
log.info("Arbitrator resending DisputeClosedMessage for trade {} after receiving updated multisig hex", trade.getId());
|
||||
HavenoUtils.arbitrationManager.closeDisputeTicket(dispute.getDisputeResultProperty().get(), dispute, dispute.getDisputeResultProperty().get().summaryNotesProperty().get(), () -> {
|
||||
completeAux();
|
||||
}, (errMessage, err) -> {
|
||||
err.printStackTrace();
|
||||
failed(err);
|
||||
});
|
||||
ticketClosed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// complete if not waiting for result
|
||||
if (!ticketClosed) completeAux();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void completeAux() {
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public SellerPreparePaymentReceivedMessage(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// check connection
|
||||
trade.checkWalletConnection();
|
||||
|
||||
// handle first time preparation
|
||||
if (processModel.getPaymentReceivedMessage() == null) {
|
||||
|
||||
// import multisig hex
|
||||
trade.importMultisigHex();
|
||||
|
||||
// verify, sign, and publish payout tx if given. otherwise create payout tx
|
||||
if (trade.getPayoutTxHex() != null) {
|
||||
log.info("Seller verifying, signing, and publishing payout tx for trade {}", trade.getId());
|
||||
trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true);
|
||||
} else {
|
||||
|
||||
// create unsigned payout tx
|
||||
log.info("Seller creating unsigned payout tx for trade {}", trade.getId());
|
||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||
trade.setPayoutTx(payoutTx);
|
||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||
}
|
||||
} else if (processModel.getPaymentReceivedMessage().getSignedPayoutTxHex() != null && !trade.isPayoutPublished()) {
|
||||
|
||||
// republish payout tx from previous message
|
||||
log.info("Seller re-verifying and publishing payout tx for trade {}", trade.getId());
|
||||
trade.verifyPayoutTx(processModel.getPaymentReceivedMessage().getSignedPayoutTxHex(), false, true);
|
||||
}
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPublishDepositTx extends TradeTask {
|
||||
public SellerPublishDepositTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
throw new RuntimeException("SellerPublishesDepositTx not implemented for xmr");
|
||||
|
||||
// final Transaction depositTx = processModel.getDepositTx();
|
||||
// processModel.getTradeWalletService().broadcastTx(depositTx,
|
||||
// new TxBroadcaster.Callback() {
|
||||
// @Override
|
||||
// public void onSuccess(Transaction transaction) {
|
||||
// if (!completed) {
|
||||
// // Now as we have published the deposit tx we set it in trade
|
||||
// trade.applyDepositTx(depositTx);
|
||||
//
|
||||
// trade.setState(Trade.State.SELLER_PUBLISHED_DEPOSIT_TX);
|
||||
//
|
||||
// processModel.getBtcWalletService().swapTradeEntryToAvailableEntry(processModel.getOffer().getId(),
|
||||
// AddressEntry.Context.RESERVED_FOR_TRADE);
|
||||
//
|
||||
// processModel.getTradeManager().requestPersistence();
|
||||
//
|
||||
// complete();
|
||||
// } else {
|
||||
// log.warn("We got the onSuccess callback called after the timeout has been triggered a complete().");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void onFailure(TxBroadcastException exception) {
|
||||
// if (!completed) {
|
||||
// failed(exception);
|
||||
// } else {
|
||||
// log.warn("We got the onFailure callback called after the timeout has been triggered a complete().");
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SellerPublishTradeStatistics extends TradeTask {
|
||||
public SellerPublishTradeStatistics(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
throw new RuntimeException("SellerPublishesTradeStatistics needs updated for XMR");
|
||||
// try {
|
||||
// runInterceptHook();
|
||||
//
|
||||
// checkNotNull(trade.getDepositTx());
|
||||
//
|
||||
// processModel.getP2PService().findPeersCapabilities(trade.getTradePeer().getNodeAddress())
|
||||
// .filter(capabilities -> capabilities.containsAll(Capability.TRADE_STATISTICS_3))
|
||||
// .ifPresentOrElse(capabilities -> {
|
||||
// // Our peer has updated, so as we are the seller we will publish the trade statistics.
|
||||
// // The peer as buyer does not publish anymore with v.1.4.0 (where Capability.TRADE_STATISTICS_3 was added)
|
||||
//
|
||||
// String referralId = processModel.getReferralIdService().getOptionalReferralId().orElse(null);
|
||||
// boolean isTorNetworkNode = model.getProcessModel().getP2PService().getNetworkNode() instanceof TorNetworkNode;
|
||||
// TradeStatistics3 tradeStatistics = TradeStatistics3.from(trade, referralId, isTorNetworkNode);
|
||||
// if (tradeStatistics.isValid()) {
|
||||
// log.info("Publishing trade statistics");
|
||||
// processModel.getP2PService().addPersistableNetworkPayload(tradeStatistics, true);
|
||||
// } else {
|
||||
// log.warn("Trade statistics are invalid. We do not publish. {}", tradeStatistics);
|
||||
// }
|
||||
//
|
||||
// complete();
|
||||
// },
|
||||
// () -> {
|
||||
// log.info("Our peer does not has updated yet, so they will publish the trade statistics. " +
|
||||
// "To avoid duplicates we do not publish from our side.");
|
||||
// complete();
|
||||
// });
|
||||
// } catch (Throwable t) {
|
||||
// failed(t);
|
||||
// }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.crypto.Sig;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.sign.SignedWitness;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.util.JsonUtil;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
|
||||
@Slf4j
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||
PaymentReceivedMessage message = null;
|
||||
SignedWitness signedWitness = null;
|
||||
|
||||
public SellerSendPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected abstract NodeAddress getReceiverNodeAddress();
|
||||
|
||||
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
|
||||
if (message == null) {
|
||||
|
||||
// sign account witness
|
||||
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||
if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
|
||||
accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||
log.info("{} {} signed and published peers account age witness", trade.getClass().getSimpleName(), trade.getId());
|
||||
}
|
||||
|
||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||
// peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox
|
||||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||
// other data stays the same when we re-send the message at any time later.
|
||||
String deterministicId = HavenoUtils.getDeterministicId(trade, PaymentReceivedMessage.class, getReceiverNodeAddress());
|
||||
message = new PaymentReceivedMessage(
|
||||
tradeId,
|
||||
processModel.getMyNodeAddress(),
|
||||
deterministicId,
|
||||
trade.isPayoutPublished() ? null : trade.getPayoutTxHex(), // unsigned
|
||||
trade.isPayoutPublished() ? trade.getPayoutTxHex() : null, // signed
|
||||
trade.getSelf().getUpdatedMultisigHex(),
|
||||
trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal(), // informs to expect payout
|
||||
trade.getTradePeer().getAccountAgeWitness(),
|
||||
signedWitness,
|
||||
processModel.getPaymentSentMessage()
|
||||
);
|
||||
|
||||
// sign message
|
||||
try {
|
||||
String messageAsJson = JsonUtil.objectToJson(message);
|
||||
byte[] sig = Sig.sign(processModel.getP2PService().getKeyRing().getSignatureKeyPair().getPrivate(), messageAsJson.getBytes(Charsets.UTF_8));
|
||||
message.setSellerSignature(sig);
|
||||
processModel.setPaymentReceivedMessage(message);
|
||||
trade.requestPersistence();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.advanceState(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} sent: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.advanceState(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||
log.error("{} failed: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.advanceState(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} stored in mailbox: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.advanceState(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||
log.info("{} arrived: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public class SellerSendPaymentReceivedMessageToArbitrator extends SellerSendPaymentReceivedMessage {
|
||||
|
||||
public SellerSendPaymentReceivedMessageToArbitrator(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getArbitrator().getNodeAddress();
|
||||
}
|
||||
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getArbitrator().getPubKeyRing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.PaymentReceivedMessage;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.trade.messages.TradeMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Slf4j
|
||||
public class SellerSendPaymentReceivedMessageToBuyer extends SellerSendPaymentReceivedMessage {
|
||||
|
||||
public SellerSendPaymentReceivedMessageToBuyer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
if (processModel.getPaymentReceivedMessage() == null) {
|
||||
processModel.setPaymentReceivedMessage((PaymentReceivedMessage) super.getTradeMailboxMessage(tradeId)); // save payment received message for buyer
|
||||
}
|
||||
return processModel.getPaymentReceivedMessage();
|
||||
}
|
||||
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getBuyer().getNodeAddress();
|
||||
}
|
||||
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getBuyer().getPubKeyRing();
|
||||
}
|
||||
|
||||
// continue execution on fault so payment received message is sent to arbitrator
|
||||
@Override
|
||||
protected void onFault(String errorMessage, TradeMessage message) {
|
||||
setStateFault();
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
complete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.HavenoUtils;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.DepositsConfirmedMessage;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTask {
|
||||
private DepositsConfirmedMessage message;
|
||||
|
||||
public SendDepositsConfirmedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected abstract NodeAddress getReceiverNodeAddress();
|
||||
|
||||
@Override
|
||||
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||
if (message == null) {
|
||||
|
||||
// export multisig hex once
|
||||
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
||||
trade.getSelf().setUpdatedMultisigHex(trade.getWallet().exportMultisigHex());
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||
// peer does not respond with an ACK msg in a certain time interval. To avoid that we get dangling mailbox
|
||||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||
// other data stays the same when we re-send the message at any time later.
|
||||
String deterministicId = HavenoUtils.getDeterministicId(trade, DepositsConfirmedMessage.class, getReceiverNodeAddress());
|
||||
message = new DepositsConfirmedMessage(
|
||||
trade.getOffer().getId(),
|
||||
processModel.getMyNodeAddress(),
|
||||
processModel.getPubKeyRing(),
|
||||
deterministicId,
|
||||
trade.getBuyer() == trade.getTradePeer(getReceiverNodeAddress()) ? trade.getSeller().getPaymentAccountKey() : null, // buyer receives seller's payment account decryption key
|
||||
trade.getSelf().getUpdatedMultisigHex());
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
// no additional handling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
// no additional handling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
// no additional handling
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
// no additional handling
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SendDepositsConfirmedMessageToArbitrator extends SendDepositsConfirmedMessage {
|
||||
|
||||
public SendDepositsConfirmedMessageToArbitrator(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getArbitrator().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getArbitrator().getPubKeyRing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SendDepositsConfirmedMessageToBuyer extends SendDepositsConfirmedMessage {
|
||||
|
||||
public SendDepositsConfirmedMessageToBuyer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getBuyer().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getBuyer().getPubKeyRing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||
*/
|
||||
@Slf4j
|
||||
public class SendDepositsConfirmedMessageToSeller extends SendDepositsConfirmedMessage {
|
||||
|
||||
public SendDepositsConfirmedMessageToSeller(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getSeller().getNodeAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getSeller().getPubKeyRing();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.trade.messages.TradeMessage;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.SendMailboxMessageListener;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class SendMailboxMessageTask extends TradeTask {
|
||||
public SendMailboxMessageTask(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
protected NodeAddress getReceiverNodeAddress() {
|
||||
return trade.getTradePeer().getNodeAddress();
|
||||
}
|
||||
|
||||
protected PubKeyRing getReceiverPubKeyRing() {
|
||||
return trade.getTradePeer().getPubKeyRing();
|
||||
}
|
||||
|
||||
protected abstract TradeMailboxMessage getTradeMailboxMessage(String tradeId);
|
||||
|
||||
protected abstract void setStateSent();
|
||||
|
||||
protected abstract void setStateArrived();
|
||||
|
||||
protected abstract void setStateStoredInMailbox();
|
||||
|
||||
protected abstract void setStateFault();
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
String id = processModel.getOfferId();
|
||||
TradeMailboxMessage message = getTradeMailboxMessage(id);
|
||||
setStateSent();
|
||||
NodeAddress peersNodeAddress = getReceiverNodeAddress();
|
||||
log.info("Send {} to peer {} for {} {}", trade.getClass().getSimpleName(), trade.getId(),
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
TradeTask task = this;
|
||||
processModel.getP2PService().getMailboxMessageService().sendEncryptedMailboxMessage(
|
||||
peersNodeAddress,
|
||||
getReceiverPubKeyRing(),
|
||||
message,
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
setStateArrived();
|
||||
if (!task.isCompleted()) complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoredInMailbox() {
|
||||
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
SendMailboxMessageTask.this.onStoredInMailbox();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}", message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
|
||||
SendMailboxMessageTask.this.onFault(errorMessage, message);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
protected void onStoredInMailbox() {
|
||||
setStateStoredInMailbox();
|
||||
if (!isCompleted()) complete();
|
||||
}
|
||||
|
||||
protected void onFault(String errorMessage, TradeMessage message) {
|
||||
setStateFault();
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
failed(errorMessage);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.btc.model.XmrAddressEntry;
|
||||
import haveno.core.offer.OfferDirection;
|
||||
import haveno.core.trade.Trade;
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import monero.daemon.model.MoneroOutput;
|
||||
import monero.wallet.model.MoneroTxWallet;
|
||||
|
||||
public class TakerReserveTradeFunds extends TradeTask {
|
||||
|
||||
public TakerReserveTradeFunds(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// create reserve tx
|
||||
BigInteger takerFee = trade.getTakerFee();
|
||||
BigInteger sendAmount = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getAmount() : BigInteger.valueOf(0);
|
||||
BigInteger securityDeposit = trade.getOffer().getDirection() == OfferDirection.BUY ? trade.getOffer().getSellerSecurityDeposit() : trade.getOffer().getBuyerSecurityDeposit();
|
||||
String returnAddress = model.getXmrWalletService().getOrCreateAddressEntry(trade.getOffer().getId(), XmrAddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
MoneroTxWallet reserveTx = model.getXmrWalletService().createReserveTx(takerFee, sendAmount, securityDeposit, returnAddress);
|
||||
|
||||
// collect reserved key images
|
||||
List<String> reservedKeyImages = new ArrayList<String>();
|
||||
for (MoneroOutput input : reserveTx.getInputs()) reservedKeyImages.add(input.getKeyImage().getHex());
|
||||
|
||||
// save process state
|
||||
processModel.setReserveTx(reserveTx);
|
||||
processModel.getTaker().setReserveTxKeyImages(reservedKeyImages);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
trade.setErrorMessage("An error occurred.\n" +
|
||||
"Error message:\n"
|
||||
+ t.getMessage());
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.app.Version;
|
||||
import haveno.common.handlers.ErrorMessageHandler;
|
||||
import haveno.common.handlers.ResultHandler;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.offer.availability.DisputeAgentSelection;
|
||||
import haveno.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.InitTradeRequest;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.SendDirectMessageListener;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class TakerSendInitTradeRequestToArbitrator extends TradeTask {
|
||||
|
||||
@SuppressWarnings({"unused"})
|
||||
public TakerSendInitTradeRequestToArbitrator(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// send request to signing arbitrator then least used arbitrators until success
|
||||
sendInitTradeRequests(trade.getOffer().getOfferPayload().getArbitratorSigner(), new HashSet<NodeAddress>(), () -> {
|
||||
complete();
|
||||
}, (errorMessage) -> {
|
||||
log.warn("Cannot initialize trade with arbitrators: " + errorMessage);
|
||||
failed(errorMessage);
|
||||
});
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendInitTradeRequests(NodeAddress arbitratorNodeAddress, Set<NodeAddress> excludedArbitrators, ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||
sendInitTradeRequest(arbitratorNodeAddress, new SendDirectMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at arbitrator: offerId={}", InitTradeRequest.class.getSimpleName(), trade.getId());
|
||||
resultHandler.handleResult();
|
||||
}
|
||||
|
||||
// if unavailable, try alternative arbitrator
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.warn("Arbitrator {} unavailable: {}", arbitratorNodeAddress, errorMessage);
|
||||
excludedArbitrators.add(arbitratorNodeAddress);
|
||||
Arbitrator altArbitrator = DisputeAgentSelection.getLeastUsedArbitrator(processModel.getTradeStatisticsManager(), processModel.getArbitratorManager(), excludedArbitrators);
|
||||
if (altArbitrator == null) {
|
||||
errorMessageHandler.handleErrorMessage("Cannot take offer because no arbitrators are available");
|
||||
return;
|
||||
}
|
||||
log.info("Using alternative arbitrator {}", altArbitrator.getNodeAddress());
|
||||
sendInitTradeRequests(altArbitrator.getNodeAddress(), excludedArbitrators, resultHandler, errorMessageHandler);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void sendInitTradeRequest(NodeAddress arbitratorNodeAddress, SendDirectMessageListener listener) {
|
||||
|
||||
// get registered arbitrator
|
||||
Arbitrator arbitrator = processModel.getUser().getAcceptedArbitratorByAddress(arbitratorNodeAddress);
|
||||
if (arbitrator == null) throw new RuntimeException("Node address " + arbitratorNodeAddress + " is not a registered arbitrator");
|
||||
|
||||
// set pub keys
|
||||
processModel.getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
|
||||
trade.getArbitrator().setNodeAddress(arbitratorNodeAddress);
|
||||
trade.getArbitrator().setPubKeyRing(processModel.getArbitrator().getPubKeyRing());
|
||||
|
||||
// create request to arbitrator
|
||||
InitTradeRequest makerRequest = (InitTradeRequest) processModel.getTradeMessage(); // taker's InitTradeRequest to maker
|
||||
InitTradeRequest arbitratorRequest = new InitTradeRequest(
|
||||
makerRequest.getTradeId(),
|
||||
makerRequest.getSenderNodeAddress(),
|
||||
makerRequest.getPubKeyRing(),
|
||||
makerRequest.getTradeAmount(),
|
||||
makerRequest.getTradePrice(),
|
||||
makerRequest.getTradeFee(),
|
||||
makerRequest.getAccountId(),
|
||||
makerRequest.getPaymentAccountId(),
|
||||
makerRequest.getPaymentMethodId(),
|
||||
makerRequest.getUid(),
|
||||
Version.getP2PMessageVersion(),
|
||||
makerRequest.getAccountAgeWitnessSignatureOfOfferId(),
|
||||
makerRequest.getCurrentDate(),
|
||||
makerRequest.getMakerNodeAddress(),
|
||||
makerRequest.getTakerNodeAddress(),
|
||||
trade.getArbitrator().getNodeAddress(),
|
||||
processModel.getReserveTx().getHash(),
|
||||
processModel.getReserveTx().getFullHex(),
|
||||
processModel.getReserveTx().getKey(),
|
||||
makerRequest.getPayoutAddress(),
|
||||
processModel.getMakerSignature());
|
||||
|
||||
// send request to arbitrator
|
||||
log.info("Sending {} with offerId {} and uid {} to arbitrator {}", arbitratorRequest.getClass().getSimpleName(), arbitratorRequest.getTradeId(), arbitratorRequest.getUid(), trade.getArbitrator().getNodeAddress());
|
||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
||||
arbitratorNodeAddress,
|
||||
arbitrator.getPubKeyRing(),
|
||||
arbitratorRequest,
|
||||
listener
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class TakerVerifyMakerFeePayment extends TradeTask {
|
||||
public TakerVerifyMakerFeePayment(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
//TODO impl. missing
|
||||
// int numOfPeersSeenTx = processModel.getWalletService().getNumOfPeersSeenTx(processModel.getTakeOfferFeeTxId().getHashAsString());
|
||||
/* if (numOfPeersSeenTx > 2) {
|
||||
resultHandler.handleResult();
|
||||
}*/
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.taskrunner.Task;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.ProcessModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public abstract class TradeTask extends Task<Trade> {
|
||||
protected final ProcessModel processModel;
|
||||
protected final Trade trade;
|
||||
|
||||
protected TradeTask(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
|
||||
this.trade = trade;
|
||||
processModel = trade.getProcessModel();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void complete() {
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed() {
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed(String message) {
|
||||
appendToErrorMessage(message);
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void failed(Throwable t) {
|
||||
t.printStackTrace();
|
||||
appendExceptionToErrorMessage(t);
|
||||
trade.setErrorMessage(errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
super.failed();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.account.witness.AccountAgeWitnessService;
|
||||
import haveno.core.locale.CurrencyUtil;
|
||||
import haveno.core.offer.Offer;
|
||||
import haveno.core.payment.payload.PaymentAccountPayload;
|
||||
import haveno.core.trade.ArbitratorTrade;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.TradePeer;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class VerifyPeersAccountAgeWitness extends TradeTask {
|
||||
|
||||
public VerifyPeersAccountAgeWitness(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
// only verify fiat offer
|
||||
Offer offer = checkNotNull(trade.getOffer());
|
||||
if (CurrencyUtil.isCryptoCurrency(offer.getCurrencyCode())) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if arbitrator
|
||||
if (trade instanceof ArbitratorTrade) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip if payment account payload is null
|
||||
TradePeer tradePeer = trade.getTradePeer();
|
||||
if (tradePeer.getPaymentAccountPayload() == null) {
|
||||
complete();
|
||||
return;
|
||||
}
|
||||
|
||||
AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||
PaymentAccountPayload peersPaymentAccountPayload = checkNotNull(tradePeer.getPaymentAccountPayload(),
|
||||
"Peers peersPaymentAccountPayload must not be null");
|
||||
PubKeyRing peersPubKeyRing = checkNotNull(tradePeer.getPubKeyRing(), "peersPubKeyRing must not be null");
|
||||
byte[] nonce = checkNotNull(tradePeer.getAccountAgeWitnessNonce());
|
||||
byte[] signature = checkNotNull(tradePeer.getAccountAgeWitnessSignature());
|
||||
AtomicReference<String> errorMsg = new AtomicReference<>();
|
||||
boolean isValid = accountAgeWitnessService.verifyAccountAgeWitness(trade,
|
||||
peersPaymentAccountPayload,
|
||||
peersPubKeyRing,
|
||||
nonce,
|
||||
signature,
|
||||
errorMsg::set);
|
||||
if (isValid) {
|
||||
trade.getTradePeer().setAccountAgeWitness(processModel.getAccountAgeWitnessService().findWitness(trade.getTradePeer().getPaymentAccountPayload(), trade.getTradePeer().getPubKeyRing()).orElse(null));
|
||||
log.info("{} {} verified witness data of peer {}", trade.getClass().getSimpleName(), trade.getId(), tradePeer.getNodeAddress());
|
||||
complete();
|
||||
} else {
|
||||
failed(errorMsg.get());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class FinalizeMediatedPayoutTx extends TradeTask {
|
||||
|
||||
public FinalizeMediatedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
throw new RuntimeException("FinalizeMediatedPayoutTx not implemented for xmr");
|
||||
|
||||
// Transaction depositTx = checkNotNull(trade.getDepositTx());
|
||||
// String tradeId = trade.getId();
|
||||
// TradePeer tradePeer = trade.getTradePeer();
|
||||
// BtcWalletService walletService = processModel.getBtcWalletService();
|
||||
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
// Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null");
|
||||
// Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
||||
//
|
||||
// checkNotNull(trade.getTradeAmount(), "trade.getTradeAmount() must not be null");
|
||||
//
|
||||
//
|
||||
// byte[] mySignature = checkNotNull(processModel.getMediatedPayoutTxSignature(),
|
||||
// "processModel.getTxSignatureFromMediation must not be null");
|
||||
// byte[] peersSignature = checkNotNull(tradePeer.getMediatedPayoutTxSignature(),
|
||||
// "tradePeer.getTxSignatureFromMediation must not be null");
|
||||
//
|
||||
// boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing());
|
||||
// byte[] buyerSignature = isMyRoleBuyer ? mySignature : peersSignature;
|
||||
// byte[] sellerSignature = isMyRoleBuyer ? peersSignature : mySignature;
|
||||
//
|
||||
// Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit());
|
||||
// Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation());
|
||||
// Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation());
|
||||
// checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)),
|
||||
// "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() +
|
||||
// "; sellerPayoutAmount=" + sellerPayoutAmount);
|
||||
//
|
||||
// String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
// String peersPayoutAddressString = tradePeer.getPayoutAddressString();
|
||||
// String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString;
|
||||
// String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString;
|
||||
//
|
||||
// byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
// byte[] peersMultiSigPubKey = tradePeer.getMultiSigPubKey();
|
||||
// byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey;
|
||||
// byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey;
|
||||
//
|
||||
// DeterministicKey multiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey);
|
||||
//
|
||||
// checkArgument(Arrays.equals(myMultiSigPubKey,
|
||||
// walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
// "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId);
|
||||
//
|
||||
// Transaction transaction = processModel.getTradeWalletService().finalizeMediatedPayoutTx(
|
||||
// depositTx,
|
||||
// buyerSignature,
|
||||
// sellerSignature,
|
||||
// buyerPayoutAmount,
|
||||
// sellerPayoutAmount,
|
||||
// buyerPayoutAddressString,
|
||||
// sellerPayoutAddressString,
|
||||
// multiSigKeyPair,
|
||||
// buyerMultiSigPubKey,
|
||||
// sellerMultiSigPubKey
|
||||
// );
|
||||
//
|
||||
// trade.setPayoutTx(transaction);
|
||||
//
|
||||
// processModel.getTradeManager().requestPersistence();
|
||||
//
|
||||
// walletService.resetCoinLockedInMultiSigAddressEntry(tradeId);
|
||||
//
|
||||
// complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.support.dispute.mediation.MediationResultState;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import haveno.core.util.Validator;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessMediatedPayoutSignatureMessage extends TradeTask {
|
||||
public ProcessMediatedPayoutSignatureMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
log.debug("current trade state " + trade.getState());
|
||||
MediatedPayoutTxSignatureMessage message = (MediatedPayoutTxSignatureMessage) processModel.getTradeMessage();
|
||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
checkNotNull(message);
|
||||
|
||||
trade.getTradePeer().setMediatedPayoutTxSignature(checkNotNull(message.getTxSignature()));
|
||||
|
||||
// update to the latest peer address of our peer if the message is correct
|
||||
trade.getTradePeer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.RECEIVED_SIG_MSG);
|
||||
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class ProcessMediatedPayoutTxPublishedMessage extends TradeTask {
|
||||
public ProcessMediatedPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
throw new RuntimeException("ProcessMediatedPayoutTxPublishedMessage not implemented for xmr");
|
||||
// MediatedPayoutTxPublishedMessage message = (MediatedPayoutTxPublishedMessage) processModel.getTradeMessage();
|
||||
// Validator.checkTradeId(processModel.getOfferId(), message);
|
||||
// checkNotNull(message);
|
||||
// checkArgument(message.getPayoutTx() != null);
|
||||
//
|
||||
// // update to the latest peer address of our peer if the message is correct
|
||||
// trade.getTradePeer().setNodeAddress(processModel.getTempTradePeerNodeAddress());
|
||||
//
|
||||
// if (trade.getPayoutTx() == null) {
|
||||
// Transaction committedMediatedPayoutTx = WalletService.maybeAddNetworkTxToWallet(message.getPayoutTx(), processModel.getBtcWalletService().getWallet());
|
||||
// trade.setPayoutTx(committedMediatedPayoutTx);
|
||||
// log.info("MediatedPayoutTx received from peer. Txid: {}\nhex: {}",
|
||||
// committedMediatedPayoutTx.getTxId().toString(), Utils.HEX.encode(committedMediatedPayoutTx.bitcoinSerialize()));
|
||||
//
|
||||
// trade.setMediationResultState(MediationResultState.RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
|
||||
//
|
||||
// if (trade.getPayoutTx() != null) {
|
||||
// // We need to delay that call as we might get executed at startup after mailbox messages are
|
||||
// // applied where we iterate over out pending trades. The closeDisputedTrade method would remove
|
||||
// // that trade from the list causing a ConcurrentModificationException.
|
||||
// // To avoid that we delay for one render frame.
|
||||
// UserThread.execute(() -> processModel.getTradeManager()
|
||||
// .closeDisputedTrade(trade.getId(), Trade.DisputeState.MEDIATION_CLOSED));
|
||||
// }
|
||||
//
|
||||
// processModel.getBtcWalletService().resetCoinLockedInMultiSigAddressEntry(trade.getId());
|
||||
// } else {
|
||||
// log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
|
||||
// }
|
||||
//
|
||||
// processModel.getTradeManager().requestPersistence();
|
||||
//
|
||||
// complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.crypto.PubKeyRing;
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.support.dispute.mediation.MediationResultState;
|
||||
import haveno.core.trade.Contract;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import haveno.network.p2p.NodeAddress;
|
||||
import haveno.network.p2p.P2PService;
|
||||
import haveno.network.p2p.SendMailboxMessageListener;
|
||||
import java.util.UUID;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
public class SendMediatedPayoutSignatureMessage extends TradeTask {
|
||||
public SendMediatedPayoutSignatureMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
PubKeyRing pubKeyRing = processModel.getPubKeyRing();
|
||||
Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
||||
PubKeyRing peersPubKeyRing = contract.getPeersPubKeyRing(pubKeyRing);
|
||||
NodeAddress peersNodeAddress = contract.getPeersNodeAddress(pubKeyRing);
|
||||
P2PService p2PService = processModel.getP2PService();
|
||||
MediatedPayoutTxSignatureMessage message = new MediatedPayoutTxSignatureMessage(processModel.getMediatedPayoutTxSignature(),
|
||||
trade.getId(),
|
||||
p2PService.getAddress(),
|
||||
UUID.randomUUID().toString());
|
||||
log.info("Send {} to peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_SENT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
p2PService.getMailboxMessageService().sendEncryptedMailboxMessage(peersNodeAddress,
|
||||
peersPubKeyRing,
|
||||
message,
|
||||
new SendMailboxMessageListener() {
|
||||
@Override
|
||||
public void onArrived() {
|
||||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStoredInMailbox() {
|
||||
log.info("{} stored in mailbox for peer {}. tradeId={}, uid={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid());
|
||||
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
complete();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFault(String errorMessage) {
|
||||
log.error("{} failed: Peer {}. tradeId={}, uid={}, errorMessage={}",
|
||||
message.getClass().getSimpleName(), peersNodeAddress, message.getTradeId(), message.getUid(), errorMessage);
|
||||
trade.setMediationResultState(MediationResultState.SIG_MSG_SEND_FAILED);
|
||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
failed(errorMessage);
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.support.dispute.mediation.MediationResultState;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.messages.TradeMailboxMessage;
|
||||
import haveno.core.trade.protocol.tasks.SendMailboxMessageTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
|
||||
@Slf4j
|
||||
public class SendMediatedPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
||||
public SendMediatedPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TradeMailboxMessage getTradeMailboxMessage(String id) {
|
||||
throw new RuntimeException("SendMediatedPayoutTxPublishedMessage.getMessage(id) not implemented for xmr");
|
||||
// Transaction payoutTx = checkNotNull(trade.getPayoutTx(), "trade.getPayoutTx() must not be null");
|
||||
// return new MediatedPayoutTxPublishedMessage(
|
||||
// id,
|
||||
// payoutTx.bitcoinSerialize(),
|
||||
// processModel.getMyNodeAddress(),
|
||||
// UUID.randomUUID().toString()
|
||||
// );
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateSent() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_SENT);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateArrived() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_ARRIVED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateStoredInMailbox() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_IN_MAILBOX);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setStateFault() {
|
||||
trade.setMediationResultState(MediationResultState.PAYOUT_TX_PUBLISHED_MSG_SEND_FAILED);
|
||||
processModel.getTradeManager().requestPersistence();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
|
||||
if (trade.getPayoutTx() == null) {
|
||||
log.error("PayoutTx is null");
|
||||
failed("PayoutTx is null");
|
||||
return;
|
||||
}
|
||||
|
||||
super.run();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SetupMediatedPayoutTxListener extends TradeTask {
|
||||
|
||||
@SuppressWarnings({ "unused" })
|
||||
public SetupMediatedPayoutTxListener(TaskRunner taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
if (true) throw new RuntimeException("Not implemented");
|
||||
complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or (at
|
||||
* your option) any later version.
|
||||
*
|
||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||
* License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package haveno.core.trade.protocol.tasks.mediation;
|
||||
|
||||
import haveno.common.taskrunner.TaskRunner;
|
||||
import haveno.core.trade.Trade;
|
||||
import haveno.core.trade.protocol.tasks.TradeTask;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
public class SignMediatedPayoutTx extends TradeTask {
|
||||
|
||||
public SignMediatedPayoutTx(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||
super(taskHandler, trade);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void run() {
|
||||
try {
|
||||
runInterceptHook();
|
||||
throw new RuntimeException("SignMediatedPayoutTx not implemented for xmr");
|
||||
|
||||
// TradePeer tradePeer = trade.getTradePeer();
|
||||
// if (processModel.getMediatedPayoutTxSignature() != null) {
|
||||
// log.warn("processModel.getTxSignatureFromMediation is already set");
|
||||
// }
|
||||
//
|
||||
// String tradeId = trade.getId();
|
||||
// BtcWalletService walletService = processModel.getBtcWalletService();
|
||||
// Transaction depositTx = checkNotNull(trade.getDepositTx(), "trade.getDepositTx() must not be null");
|
||||
// Offer offer = checkNotNull(trade.getOffer(), "offer must not be null");
|
||||
// Coin tradeAmount = checkNotNull(trade.getTradeAmount(), "tradeAmount must not be null");
|
||||
// Contract contract = checkNotNull(trade.getContract(), "contract must not be null");
|
||||
//
|
||||
// Coin totalPayoutAmount = offer.getBuyerSecurityDeposit().add(tradeAmount).add(offer.getSellerSecurityDeposit());
|
||||
// Coin buyerPayoutAmount = Coin.valueOf(processModel.getBuyerPayoutAmountFromMediation());
|
||||
// Coin sellerPayoutAmount = Coin.valueOf(processModel.getSellerPayoutAmountFromMediation());
|
||||
//
|
||||
// checkArgument(totalPayoutAmount.equals(buyerPayoutAmount.add(sellerPayoutAmount)),
|
||||
// "Payout amount does not match buyerPayoutAmount=" + buyerPayoutAmount.toFriendlyString() +
|
||||
// "; sellerPayoutAmount=" + sellerPayoutAmount);
|
||||
//
|
||||
// boolean isMyRoleBuyer = contract.isMyRoleBuyer(processModel.getPubKeyRing());
|
||||
//
|
||||
// String myPayoutAddressString = walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.TRADE_PAYOUT).getAddressString();
|
||||
// String peersPayoutAddressString = tradePeer.getPayoutAddressString();
|
||||
// String buyerPayoutAddressString = isMyRoleBuyer ? myPayoutAddressString : peersPayoutAddressString;
|
||||
// String sellerPayoutAddressString = isMyRoleBuyer ? peersPayoutAddressString : myPayoutAddressString;
|
||||
//
|
||||
// byte[] myMultiSigPubKey = processModel.getMyMultiSigPubKey();
|
||||
// byte[] peersMultiSigPubKey = tradePeer.getMultiSigPubKey();
|
||||
// byte[] buyerMultiSigPubKey = isMyRoleBuyer ? myMultiSigPubKey : peersMultiSigPubKey;
|
||||
// byte[] sellerMultiSigPubKey = isMyRoleBuyer ? peersMultiSigPubKey : myMultiSigPubKey;
|
||||
//
|
||||
// DeterministicKey myMultiSigKeyPair = walletService.getMultiSigKeyPair(tradeId, myMultiSigPubKey);
|
||||
//
|
||||
// checkArgument(Arrays.equals(myMultiSigPubKey,
|
||||
// walletService.getOrCreateAddressEntry(tradeId, AddressEntry.Context.MULTI_SIG).getPubKey()),
|
||||
// "myMultiSigPubKey from AddressEntry must match the one from the trade data. trade id =" + tradeId);
|
||||
//
|
||||
// byte[] mediatedPayoutTxSignature = processModel.getTradeWalletService().signMediatedPayoutTx(
|
||||
// depositTx,
|
||||
// buyerPayoutAmount,
|
||||
// sellerPayoutAmount,
|
||||
// buyerPayoutAddressString,
|
||||
// sellerPayoutAddressString,
|
||||
// myMultiSigKeyPair,
|
||||
// buyerMultiSigPubKey,
|
||||
// sellerMultiSigPubKey);
|
||||
// processModel.setMediatedPayoutTxSignature(mediatedPayoutTxSignature);
|
||||
//
|
||||
// processModel.getTradeManager().requestPersistence();
|
||||
//
|
||||
// complete();
|
||||
} catch (Throwable t) {
|
||||
failed(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue