rename all packages and other names from bisq to haveno

This commit is contained in:
woodser 2023-03-06 19:14:00 -05:00
parent ab0b9e3b77
commit 1a1fb130c0
1775 changed files with 14575 additions and 16767 deletions

View file

@ -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);
}
}
}

View file

@ -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();
}
});
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
// }
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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
);
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}