improve error handling in protocol pipelines

support getTrades() from grpc api
consistently use timeouts in protocol pipelines
remove trade and repost offer on protocol errors
delete multisig wallet when trade removed
protocol advances on ack messages instead of network onArrived()
re-order protocol class methods to correct order
This commit is contained in:
woodser 2021-12-14 12:43:45 -05:00
parent d2ddfad5bc
commit 7c9c35b1b8
21 changed files with 1096 additions and 930 deletions

View file

@ -39,6 +39,7 @@ import bisq.proto.grpc.GetPaymentAccountFormRequest;
import bisq.proto.grpc.GetPaymentAccountsRequest; import bisq.proto.grpc.GetPaymentAccountsRequest;
import bisq.proto.grpc.GetPaymentMethodsRequest; import bisq.proto.grpc.GetPaymentMethodsRequest;
import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetTradeRequest;
import bisq.proto.grpc.GetTradesRequest;
import bisq.proto.grpc.GetTransactionRequest; import bisq.proto.grpc.GetTransactionRequest;
import bisq.proto.grpc.GetTxFeeRateRequest; import bisq.proto.grpc.GetTxFeeRateRequest;
import bisq.proto.grpc.GetVersionRequest; import bisq.proto.grpc.GetVersionRequest;
@ -349,6 +350,11 @@ public final class GrpcClient {
return grpcStubs.tradesService.getTrade(request).getTrade(); return grpcStubs.tradesService.getTrade(request).getTrade();
} }
public List<TradeInfo> getTrades() {
var request = GetTradesRequest.newBuilder().build();
return grpcStubs.tradesService.getTrades(request).getTradesList();
}
public void confirmPaymentStarted(String tradeId) { public void confirmPaymentStarted(String tradeId) {
var request = ConfirmPaymentStartedRequest.newBuilder() var request = ConfirmPaymentStartedRequest.newBuilder()
.setTradeId(tradeId) .setTradeId(tradeId)

View file

@ -244,11 +244,15 @@ public class CoreApi {
String paymentAccountId, String paymentAccountId,
Consumer<Trade> resultHandler, Consumer<Trade> resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
try {
Offer offer = coreOffersService.getOffer(offerId); Offer offer = coreOffersService.getOffer(offerId);
coreTradesService.takeOffer(offer, coreTradesService.takeOffer(offer,
paymentAccountId, paymentAccountId,
resultHandler, resultHandler,
errorMessageHandler); errorMessageHandler);
} catch (Exception e) {
errorMessageHandler.handleErrorMessage(e.getMessage());
}
} }
public void confirmPaymentStarted(String tradeId) { public void confirmPaymentStarted(String tradeId) {
@ -271,6 +275,10 @@ public class CoreApi {
return coreTradesService.getTrade(tradeId); return coreTradesService.getTrade(tradeId);
} }
public List<Trade> getTrades() {
return coreTradesService.getTrades();
}
public String getTradeRole(String tradeId) { public String getTradeRole(String tradeId) {
return coreTradesService.getTradeRole(tradeId); return coreTradesService.getTradeRole(tradeId);
} }

View file

@ -45,6 +45,7 @@ import java.util.ArrayList;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -142,11 +143,15 @@ class CoreOffersService {
} }
List<Offer> getMyOffers(String direction, String currencyCode) { List<Offer> getMyOffers(String direction, String currencyCode) {
// get my offers posted to books
List<Offer> offers = offerBookService.getOffers().stream() List<Offer> offers = offerBookService.getOffers().stream()
.filter(o -> o.isMyOffer(keyRing)) .filter(o -> o.isMyOffer(keyRing))
.filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode)) .filter(o -> offerMatchesDirectionAndCurrency(o, direction, currencyCode))
.sorted(priceComparator(direction)) .sorted(priceComparator(direction))
.collect(Collectors.toList()); .collect(Collectors.toList());
// remove unreserved offers
Set<Offer> unreservedOffers = getUnreservedOffers(offers); Set<Offer> unreservedOffers = getUnreservedOffers(offers);
offers.removeAll(unreservedOffers); offers.removeAll(unreservedOffers);
@ -156,6 +161,13 @@ class CoreOffersService {
unreservedOpenOffers.add(openOfferManager.getOpenOfferById(unreservedOffer.getId()).get()); unreservedOpenOffers.add(openOfferManager.getOpenOfferById(unreservedOffer.getId()).get());
} }
openOfferManager.removeOpenOffers(unreservedOpenOffers, null); openOfferManager.removeOpenOffers(unreservedOpenOffers, null);
// set offer state
for (Offer offer : offers) {
Optional<OpenOffer> openOffer = openOfferManager.getOpenOfferById(offer.getId());
if (openOffer.isPresent()) offer.setState(openOffer.get().getState() == OpenOffer.State.AVAILABLE ? Offer.State.AVAILABLE : Offer.State.NOT_AVAILABLE);
}
return offers; return offers;
} }

View file

@ -38,7 +38,8 @@ import org.bitcoinj.core.Coin;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -89,6 +90,7 @@ class CoreTradesService {
String paymentAccountId, String paymentAccountId,
Consumer<Trade> resultHandler, Consumer<Trade> resultHandler,
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
try {
coreWalletsService.verifyWalletsAreAvailable(); coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked(); coreWalletsService.verifyEncryptedWalletIsUnlocked();
@ -114,6 +116,9 @@ class CoreTradesService {
resultHandler::accept, resultHandler::accept,
errorMessageHandler errorMessageHandler
); );
} catch (Exception e) {
errorMessageHandler.handleErrorMessage(e.getMessage());
}
} }
void confirmPaymentStarted(String tradeId) { void confirmPaymentStarted(String tradeId) {
@ -224,6 +229,14 @@ class CoreTradesService {
return tradable.filter((t) -> t instanceof Trade).map(value -> (Trade) value); return tradable.filter((t) -> t instanceof Trade).map(value -> (Trade) value);
} }
List<Trade> getTrades() {
coreWalletsService.verifyWalletsAreAvailable();
coreWalletsService.verifyEncryptedWalletIsUnlocked();
List<Trade> trades = new ArrayList<Trade>(tradeManager.getTrades());
trades.addAll(closedTradableManager.getClosedTrades());
return trades;
}
private boolean isFollowingBuyerProtocol(Trade trade) { private boolean isFollowingBuyerProtocol(Trade trade) {
return tradeManager.getTradeProtocol(trade) instanceof BuyerProtocol; return tradeManager.getTradeProtocol(trade) instanceof BuyerProtocol;
} }

View file

@ -90,12 +90,14 @@ public class MoneroWalletRpcManager {
* Stop an instance of monero-wallet-rpc. * Stop an instance of monero-wallet-rpc.
* *
* @param walletRpc the client connected to the monero-wallet-rpc instance to stop * @param walletRpc the client connected to the monero-wallet-rpc instance to stop
* @param save specifies if the wallet should be saved before closing
*/ */
public void stopInstance(MoneroWalletRpc walletRpc) { public void stopInstance(MoneroWalletRpc walletRpc, boolean save) {
boolean found = false; boolean found = false;
for (Map.Entry<Integer, MoneroWalletRpc> entry : registeredPorts.entrySet()) { for (Map.Entry<Integer, MoneroWalletRpc> entry : registeredPorts.entrySet()) {
if (walletRpc == entry.getValue()) { if (walletRpc == entry.getValue()) {
walletRpc.stop(); walletRpc.close(save);
walletRpc.stopProcess();
found = true; found = true;
try { unregisterPort(entry.getKey()); } try { unregisterPort(entry.getKey()); }
catch (Exception e) { throw new MoneroError(e); } catch (Exception e) { throw new MoneroError(e); }

View file

@ -88,8 +88,6 @@ import static bisq.common.util.Preconditions.checkDir;
import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Preconditions.checkState;
import monero.common.MoneroUtils; import monero.common.MoneroUtils;
import monero.daemon.MoneroDaemon; import monero.daemon.MoneroDaemon;
import monero.daemon.MoneroDaemonRpc; import monero.daemon.MoneroDaemonRpc;
@ -132,7 +130,8 @@ public class WalletConfig extends AbstractIdleService {
private static final String MONERO_DAEMON_USERNAME = "superuser"; private static final String MONERO_DAEMON_USERNAME = "superuser";
private static final String MONERO_DAEMON_PASSWORD = "abctesting123"; private static final String MONERO_DAEMON_PASSWORD = "abctesting123";
private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager(); private static final MoneroWalletRpcManager MONERO_WALLET_RPC_MANAGER = new MoneroWalletRpcManager();
private static final String MONERO_WALLET_RPC_PATH = System.getProperty("user.dir") + File.separator + ".localnet" + File.separator + "monero-wallet-rpc"; // use wallet rpc in .localnet private static final String MONERO_WALLET_RPC_DIR = System.getProperty("user.dir") + File.separator + ".localnet"; // .localnet contains monero-wallet-rpc and wallet files
private static final String MONERO_WALLET_RPC_PATH = MONERO_WALLET_RPC_DIR + File.separator + "monero-wallet-rpc";
private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user"; private static final String MONERO_WALLET_RPC_USERNAME = "rpc_user";
private static final String MONERO_WALLET_RPC_PASSWORD = "abc123"; private static final String MONERO_WALLET_RPC_PASSWORD = "abc123";
private static final long MONERO_WALLET_SYNC_RATE = 5000l; private static final long MONERO_WALLET_SYNC_RATE = 5000l;
@ -292,6 +291,11 @@ public class WalletConfig extends AbstractIdleService {
// Meant to be overridden by subclasses // Meant to be overridden by subclasses
} }
public boolean walletExists(String walletName) {
String path = directory.toString() + File.separator + walletName;
return new File(path + ".keys").exists();
}
public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) { public MoneroWalletRpc createWallet(MoneroWalletConfig config, Integer port) {
// start monero-wallet-rpc instance // start monero-wallet-rpc instance
@ -304,7 +308,7 @@ public class WalletConfig extends AbstractIdleService {
return walletRpc; return walletRpc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc); WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e; throw e;
} }
} }
@ -321,7 +325,7 @@ public class WalletConfig extends AbstractIdleService {
return walletRpc; return walletRpc;
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc); WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance(walletRpc, false);
throw e; throw e;
} }
} }
@ -347,8 +351,17 @@ public class WalletConfig extends AbstractIdleService {
return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(cmd); return WalletConfig.MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
} }
public void closeWallet(MoneroWallet walletRpc) { public void closeWallet(MoneroWallet walletRpc, boolean save) {
WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc); WalletConfig.MONERO_WALLET_RPC_MANAGER.stopInstance((MoneroWalletRpc) walletRpc, save);
}
public void deleteWallet(String walletName) {
if (!walletExists(walletName)) throw new Error("Wallet does not exist at path: " + walletName);
String path = directory.toString() + File.separator + walletName;
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".keys").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
//WalletsSetup.deleteRollingBackup(walletName); // TODO (woodser): necessary to delete rolling backup?
} }
@Override @Override

View file

@ -86,7 +86,7 @@ public class XmrWalletService {
// TODO (woodser): wallet has single password which is passed here? // TODO (woodser): wallet has single password which is passed here?
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse // TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
public MoneroWallet createMultisigWallet(String tradeId) { public synchronized MoneroWallet createMultisigWallet(String tradeId) {
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId; String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null; MoneroWallet multisigWallet = null;
@ -99,7 +99,7 @@ public class XmrWalletService {
return multisigWallet; return multisigWallet;
} }
public MoneroWallet getMultisigWallet(String tradeId) { public synchronized MoneroWallet getMultisigWallet(String tradeId) {
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId); if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
String path = "xmr_multisig_trade_" + tradeId; String path = "xmr_multisig_trade_" + tradeId;
MoneroWallet multisigWallet = null; MoneroWallet multisigWallet = null;
@ -112,6 +112,19 @@ public class XmrWalletService {
return multisigWallet; return multisigWallet;
} }
public synchronized boolean deleteMultisigWallet(String tradeId) {
String walletName = "xmr_multisig_trade_" + tradeId;
if (!walletsSetup.getWalletConfig().walletExists(walletName)) return false;
try {
walletsSetup.getWalletConfig().closeWallet(getMultisigWallet(tradeId), false);
} catch (Exception err) {
// multisig wallet may not be open
}
walletsSetup.getWalletConfig().deleteWallet(walletName);
multisigWallets.remove(tradeId);
return true;
}
public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) { public XmrAddressEntry recoverAddressEntry(String offerId, String address, XmrAddressEntry.Context context) {
var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE); var available = findAddressEntry(address, XmrAddressEntry.Context.AVAILABLE);
if (!available.isPresent()) if (!available.isPresent())
@ -291,7 +304,7 @@ public class XmrWalletService {
threads.add(new Thread(new Runnable() { threads.add(new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { walletsSetup.getWalletConfig().closeWallet(openWallet); } try { walletsSetup.getWalletConfig().closeWallet(openWallet, true); }
catch (Exception e) { catch (Exception e) {
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?"); log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
} }

View file

@ -392,11 +392,6 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
buyerSecurityDeposit, buyerSecurityDeposit,
createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit)); createOfferService.getSellerSecurityDepositAsDouble(buyerSecurityDeposit));
if (placeOfferProtocols.containsKey(offer.getId())) {
log.warn("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
throw new RuntimeException("We already have a place offer protocol for offer " + offer.getId() + ", ignoring");
}
PlaceOfferModel model = new PlaceOfferModel(offer, PlaceOfferModel model = new PlaceOfferModel(offer,
reservedFundsForOffer, reservedFundsForOffer,
useSavingsWallet, useSavingsWallet,
@ -596,6 +591,10 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
requestPersistence(); requestPersistence();
} }
public void unreserveOpenOffer(OpenOffer openOffer) {
openOffer.setState(OpenOffer.State.AVAILABLE);
requestPersistence();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Getters // Getters

View file

@ -64,7 +64,7 @@ import bisq.network.p2p.DecryptedMessageWithPubKey;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.P2PService; import bisq.network.p2p.P2PService;
import bisq.network.p2p.network.TorNetworkNode; import bisq.network.p2p.network.TorNetworkNode;
import com.google.common.collect.ImmutableList;
import bisq.common.ClockWatcher; import bisq.common.ClockWatcher;
import bisq.common.config.Config; import bisq.common.config.Config;
import bisq.common.crypto.KeyRing; import bisq.common.crypto.KeyRing;
@ -445,8 +445,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) log.warn("Arbitrator error during trade initialization: " + errorMessage);
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); // TODO (woodser): separate handler? // TODO (woodser): ensure failed trade removed removeTrade(trade);
}); });
requestPersistence(); requestPersistence();
@ -473,7 +473,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return; return;
} }
openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? openOfferManager.reserveOpenOffer(openOffer); // TODO (woodser): reserve offer if arbitrator? probably. or, arbitrator does not have open offer?
Trade trade; Trade trade;
if (offer.isBuyOffer()) if (offer.isBuyOffer())
@ -512,9 +512,10 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
tradableList.add(trade); tradableList.add(trade);
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> { ((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
if (takeOfferRequestErrorMessageHandler != null) { log.warn("Maker error during trade initialization: " + errorMessage);
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); openOfferManager.unreserveOpenOffer(openOffer); // offer remains available // TODO: only unreserve if funds not deposited to multisig
} removeTrade(trade);
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}); });
requestPersistence(); requestPersistence();
@ -532,13 +533,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(request.getTradeId()); Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + request.getTradeId());
getTradeProtocol(trade).handleInitMultisigRequest(request, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleInitMultisigRequest(request, peer);
} }
private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) { private void handleSignContractRequest(SignContractRequest request, NodeAddress peer) {
@ -552,13 +552,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(request.getTradeId()); Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + request.getTradeId());
getTradeProtocol(trade).handleSignContractRequest(request, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleSignContractRequest(request, peer);
} }
private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) { private void handleSignContractResponse(SignContractResponse request, NodeAddress peer) {
@ -572,13 +571,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(request.getTradeId()); Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + request.getTradeId());
((TraderProtocol) getTradeProtocol(trade)).handleSignContractResponse(request, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handleSignContractResponse(request, peer);
} }
private void handleDepositRequest(DepositRequest request, NodeAddress peer) { private void handleDepositRequest(DepositRequest request, NodeAddress peer) {
@ -592,13 +590,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(request.getTradeId()); Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + request.getTradeId());
((ArbitratorProtocol) getTradeProtocol(trade)).handleDepositRequest(request, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
((ArbitratorProtocol) getTradeProtocol(trade)).handleDepositRequest(request, peer);
} }
private void handleDepositResponse(DepositResponse response, NodeAddress peer) { private void handleDepositResponse(DepositResponse response, NodeAddress peer) {
@ -612,13 +609,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(response.getTradeId()); Optional<Trade> tradeOptional = getTradeById(response.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + response.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + response.getTradeId());
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer);
} }
private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) { private void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer) {
@ -632,13 +628,12 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
} }
Optional<Trade> tradeOptional = getTradeById(request.getTradeId()); Optional<Trade> tradeOptional = getTradeById(request.getTradeId());
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) {
Trade trade = tradeOptional.get(); log.warn("No trade with id " + request.getTradeId());
((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer, errorMessage -> { return;
if (takeOfferRequestErrorMessageHandler != null) {
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
} }
}); Trade trade = tradeOptional.get();
((TraderProtocol) getTradeProtocol(trade)).handlePaymentAccountPayloadRequest(request, peer);
} }
private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) { private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
@ -655,6 +650,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
Trade trade = tradeOptional.get(); Trade trade = tradeOptional.get();
getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> { getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> {
log.warn("Error handling UpdateMultisigRequest: " + errorMessage);
if (takeOfferRequestErrorMessageHandler != null) if (takeOfferRequestErrorMessageHandler != null)
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage); takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
}); });
@ -735,7 +731,9 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
// take offer and persist trade on success // take offer and persist trade on success
((TakerProtocol) tradeProtocol).onTakeOffer(result -> { ((TakerProtocol) tradeProtocol).onTakeOffer(result -> {
tradeResultHandler.handleResult(trade); tradeResultHandler.handleResult(trade);
requestPersistence();
}, errorMessage -> { }, errorMessage -> {
log.warn("Taker error during trade initialization: " + errorMessage);
removeTrade(trade); removeTrade(trade);
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
}); });
@ -985,8 +983,25 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst(); return tradableList.stream().filter(e -> e.getId().equals(tradeId)).findFirst();
} }
public List<Trade> getTrades() {
return ImmutableList.copyOf(getObservableList().stream()
.filter(e -> e instanceof Trade)
.map(e -> e)
.collect(Collectors.toList()));
}
private void removeTrade(Trade trade) { private void removeTrade(Trade trade) {
if (tradableList.remove(trade)) { if (tradableList.remove(trade)) {
// unreserve taker trade key images
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
for (String keyImage : trade.getSelf().getReserveTxKeyImages()) {
xmrWalletService.getWallet().thawOutput(keyImage);
}
}
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
xmrWalletService.deleteMultisigWallet(trade.getId());
requestPersistence(); requestPersistence();
} }
} }

View file

@ -31,7 +31,10 @@ public class ArbitratorProtocol extends DisputeProtocol {
// Incoming messages // Incoming messages
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public void handleInitTradeRequest(InitTradeRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { // TODO (woodser): update impl to use errorMessageHandler public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
this.errorMessageHandler = errorMessageHandler;
processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set processModel.setTradeMessage(message); // TODO (woodser): confirm these are null without being set
//processModel.setTempTradingPeerNodeAddress(peer); //processModel.setTempTradingPeerNodeAddress(peer);
expect(phase(Trade.Phase.INIT) expect(phase(Trade.Phase.INIT)
@ -41,12 +44,21 @@ public class ArbitratorProtocol extends DisputeProtocol {
ApplyFilter.class, ApplyFilter.class,
ProcessInitTradeRequest.class, ProcessInitTradeRequest.class,
ArbitratorProcessesReserveTx.class, ArbitratorProcessesReserveTx.class,
ArbitratorSendsInitTradeAndMultisigRequests.class)) ArbitratorSendsInitTradeAndMultisigRequests.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }
@Override @Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleInitMultisigRequest()"); System.out.println("ArbitratorProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request); Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); processModel.setTradeMessage(request);
@ -62,12 +74,13 @@ public class ArbitratorProtocol extends DisputeProtocol {
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage); handleTaskRunnerFault(sender, request, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }
@Override @Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleSignContractRequest()"); System.out.println("ArbitratorProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message); Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
@ -84,11 +97,12 @@ public class ArbitratorProtocol extends DisputeProtocol {
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage); handleTaskRunnerFault(sender, message, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }
public void handleDepositRequest(DepositRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleDepositRequest(DepositRequest request, NodeAddress sender) {
System.out.println("ArbitratorProtocol.handleDepositRequest()"); System.out.println("ArbitratorProtocol.handleDepositRequest()");
Validator.checkTradeId(processModel.getOfferId(), request); Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); processModel.setTradeMessage(request);
@ -104,7 +118,8 @@ public class ArbitratorProtocol extends DisputeProtocol {
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage); handleTaskRunnerFault(sender, request, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }

View file

@ -64,11 +64,179 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
super(trade); super(trade);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
this.errorMessageHandler = errorMessageHandler;
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
MakerSendsInitTradeRequestIfUnreserved.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class,
MakerRemovesOpenOffer.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Handle take offer request // User interaction
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentStarted(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Payout tx
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
// TODO (woodser): remove or ignore any unsupported requests // TODO (woodser): remove or ignore any unsupported requests
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -96,175 +264,4 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) { protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) {
super.handle(message, peer); super.handle(message, peer);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentStarted(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Payout tx
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
MakerSendsInitTradeRequestIfUnreserved.class,
MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
} }

View file

@ -68,8 +68,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol { public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol {
private TradeResultHandler tradeResultHandler;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -85,7 +83,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Take offer // User interaction: Take offer
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol // TODO (woodser): this implementation is duplicated with SellerAsTakerProtocol
@ -93,7 +91,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
public void onTakeOffer(TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) { public void onTakeOffer(TradeResultHandler tradeResultHandler, ErrorMessageHandler errorMessageHandler) {
System.out.println("onTakeOffer()"); System.out.println("onTakeOffer()");
this.tradeResultHandler = tradeResultHandler; this.tradeResultHandler = tradeResultHandler;
this.errorMessageHandler = errorMessageHandler;
expect(phase(Trade.Phase.INIT) expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER) .with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress())) .from(trade.getTradingPeerNodeAddress()))
@ -108,6 +106,168 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
.executeTasks(); .executeTasks();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// TakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
tradeResultHandler.handleResult(trade); // trade is initialized
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentStarted(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Payout tx
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
if (message instanceof InputsForDepositTxResponse) {
handle((InputsForDepositTxResponse) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
// TODO (woodser): remove unused
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process // Incoming messages Take offer process
@ -149,167 +309,4 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) { protected void handle(DepositTxAndDelayedPayoutTxMessage message, NodeAddress peer) {
super.handle(message, peer); super.handle(message, peer);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentStarted(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentStarted(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message Payout tx
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(PayoutTxPublishedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Message dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
if (message instanceof InputsForDepositTxResponse) {
handle((InputsForDepositTxResponse) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// TakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with SellerAsTakerProtocol due to single inheritance
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
tradeResultHandler.handleResult(trade); // trade is initialized
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
} }

View file

@ -64,6 +64,186 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
super(trade); super(trade);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
this.errorMessageHandler = errorMessageHandler;
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
MakerSendsInitTradeRequestIfUnreserved.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class,
MakerRemovesOpenOffer.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentReceived(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Massage dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof DepositTxMessage) {
handle((DepositTxMessage) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
// TODO (woodser): remove unused
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process // Incoming messages Take offer process
@ -94,179 +274,4 @@ public class SellerAsMakerProtocol extends SellerProtocol implements MakerProtoc
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) { protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
super.handle(message, peer); super.handle(message, peer);
} }
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentReceived(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Massage dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof DepositTxMessage) {
handle((DepositTxMessage) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return MakerVerifyTakerFeePayment.class;
}
///////////////////////////////////////////////////////////////////////////////////////////
// MakerProtocol
///////////////////////////////////////////////////////////////////////////////////////////
// TODO (woodser): these methods are duplicated with BuyerAsMakerProtocol due to single inheritance
@Override
public void handleInitTradeRequest(InitTradeRequest message,
NodeAddress peer,
ErrorMessageHandler errorMessageHandler) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
ProcessInitTradeRequest.class,
//ApplyFilter.class, // TODO (woodser): these checks apply when maker signs availability request, but not here
//VerifyPeersAccountAgeWitness.class, // TODO (woodser): these checks apply after in multisig, means if rejected need to reimburse other's fee
MakerSendsInitTradeRequestIfUnreserved.class,
MakerRemovesOpenOffer.class).
using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(peer, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(peer, message, errorMessage);
}))
.withTimeout(30))
.executeTasks();
}
@Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(request)
.from(sender))
.setup(tasks(
ProcessInitMultisigRequest.class,
SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message);
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); // TODO (woodser): synchronize access since concurrent requests processed
expect(anyPhase(Trade.Phase.INIT)
.with(message)
.from(sender))
.setup(tasks(
// TODO (woodser): validate request
ProcessSignContractResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, message);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage);
})))
.executeTasks();
}
@Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(response)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessDepositResponse.class)
.using(new TradeTaskRunner(trade,
() -> {
handleTaskRunnerSuccess(sender, response);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage);
})))
.executeTasks();
}
@Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) {
System.out.println("BuyerAsMakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request);
expect(anyPhase(Trade.Phase.INIT, Trade.Phase.DEPOSIT_PUBLISHED)
.with(request)
.from(sender)) // TODO (woodser): ensure this asserts sender == response.getSenderNodeAddress()
.setup(tasks(
// TODO (woodser): validate request
ProcessPaymentAccountPayloadRequest.class)
.using(new TradeTaskRunner(trade,
() -> {
stopTimeout();
handleTaskRunnerSuccess(sender, request);
},
errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage);
})))
.executeTasks();
}
} }

View file

@ -63,8 +63,6 @@ import static com.google.common.base.Preconditions.checkNotNull;
@Slf4j @Slf4j
public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol { public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtocol {
private TradeResultHandler tradeResultHandler;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -86,7 +84,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
ErrorMessageHandler errorMessageHandler) { ErrorMessageHandler errorMessageHandler) {
System.out.println("onTakeOffer()"); System.out.println("onTakeOffer()");
this.tradeResultHandler = tradeResultHandler; this.tradeResultHandler = tradeResultHandler;
this.errorMessageHandler = errorMessageHandler;
expect(phase(Trade.Phase.INIT) expect(phase(Trade.Phase.INIT)
.with(TakerEvent.TAKE_OFFER) .with(TakerEvent.TAKE_OFFER)
.from(trade.getTradingPeerNodeAddress())) .from(trade.getTradingPeerNodeAddress()))
@ -101,73 +99,6 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
.executeTasks(); .executeTasks();
} }
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
///////////////////////////////////////////////////////////////////////////////////////////
private void handle(InputsForDepositTxResponse message, NodeAddress peer) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
//TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
SellerAsTakerSignsDepositTx.class,
SellerCreatesDelayedPayoutTx.class,
SellerSignsDelayedPayoutTx.class,
SellerSendDelayedPayoutTxSignatureRequest.class)
.withTimeout(60))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentReceived(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Massage dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof InputsForDepositTxResponse) {
handle((InputsForDepositTxResponse) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// TakerProtocol // TakerProtocol
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -175,7 +106,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
// TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance // TODO (woodser): these methods are duplicated with BuyerAsTakerProtocol due to single inheritance
@Override @Override
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()"); System.out.println("BuyerAsTakerProtocol.handleInitMultisigRequest()");
Validator.checkTradeId(processModel.getOfferId(), request); Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); processModel.setTradeMessage(request);
@ -187,20 +118,18 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
SendSignContractRequestAfterMultisig.class) SendSignContractRequestAfterMultisig.class)
.using(new TradeTaskRunner(trade, .using(new TradeTaskRunner(trade,
() -> { () -> {
System.out.println("handle multisig pipeline completed successfully!");
handleTaskRunnerSuccess(sender, request); handleTaskRunnerSuccess(sender, request);
}, },
errorMessage -> { errorMessage -> {
System.out.println("error in handle multisig pipeline!!!: " + errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage); handleTaskRunnerFault(sender, request, errorMessage);
errorMessageHandler.handleErrorMessage(errorMessage);
})) }))
.withTimeout(30)) .withTimeout(30))
.executeTasks(); .executeTasks();
} }
@Override @Override
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleSignContractRequest()"); System.out.println("SellerAsTakerProtocol.handleSignContractRequest()");
Validator.checkTradeId(processModel.getOfferId(), message); Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); processModel.setTradeMessage(message);
@ -223,7 +152,7 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
} }
@Override @Override
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleSignContractResponse()"); System.out.println("SellerAsTakerProtocol.handleSignContractResponse()");
Validator.checkTradeId(processModel.getOfferId(), message); Validator.checkTradeId(processModel.getOfferId(), message);
processModel.setTradeMessage(message); processModel.setTradeMessage(message);
@ -240,12 +169,13 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, message, errorMessage); handleTaskRunnerFault(sender, message, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }
@Override @Override
public void handleDepositResponse(DepositResponse response, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handleDepositResponse()"); System.out.println("SellerAsTakerProtocol.handleDepositResponse()");
Validator.checkTradeId(processModel.getOfferId(), response); Validator.checkTradeId(processModel.getOfferId(), response);
processModel.setTradeMessage(response); processModel.setTradeMessage(response);
@ -262,12 +192,13 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, response, errorMessage); handleTaskRunnerFault(sender, response, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks(); .executeTasks();
} }
@Override @Override
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender, ErrorMessageHandler errorMessageHandler) { public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress sender) {
System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()"); System.out.println("SellerAsTakerProtocol.handlePaymentAccountPayloadRequest()");
Validator.checkTradeId(processModel.getOfferId(), request); Validator.checkTradeId(processModel.getOfferId(), request);
processModel.setTradeMessage(request); processModel.setTradeMessage(request);
@ -286,7 +217,74 @@ public class SellerAsTakerProtocol extends SellerProtocol implements TakerProtoc
errorMessage -> { errorMessage -> {
errorMessageHandler.handleErrorMessage(errorMessage); errorMessageHandler.handleErrorMessage(errorMessage);
handleTaskRunnerFault(sender, request, errorMessage); handleTaskRunnerFault(sender, request, errorMessage);
}))) }))
.withTimeout(30))
.executeTasks();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming message when buyer has clicked payment started button
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which messages we expect
@Override
protected void handle(CounterCurrencyTransferStartedMessage message, NodeAddress peer) {
super.handle(message, peer);
}
///////////////////////////////////////////////////////////////////////////////////////////
// User interaction
///////////////////////////////////////////////////////////////////////////////////////////
// We keep the handler here in as well to make it more transparent which events we expect
@Override
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
super.onPaymentReceived(resultHandler, errorMessageHandler);
}
///////////////////////////////////////////////////////////////////////////////////////////
// Massage dispatcher
///////////////////////////////////////////////////////////////////////////////////////////
@Override
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
super.onTradeMessage(message, peer);
log.info("Received {} from {} with tradeId {} and uid {}",
message.getClass().getSimpleName(), peer, message.getTradeId(), message.getUid());
if (message instanceof InputsForDepositTxResponse) {
handle((InputsForDepositTxResponse) message, peer);
}
}
@Override
protected Class<? extends TradeTask> getVerifyPeersFeePaymentClass() {
return TakerVerifyMakerFeePayment.class;
}
// TODO (woodser): remove unused calls and classes
///////////////////////////////////////////////////////////////////////////////////////////
// Incoming messages Take offer process
///////////////////////////////////////////////////////////////////////////////////////////
private void handle(InputsForDepositTxResponse message, NodeAddress peer) {
expect(phase(Trade.Phase.INIT)
.with(message)
.from(peer))
.setup(tasks(
TakerProcessesInputsForDepositTxResponse.class,
ApplyFilter.class,
VerifyPeersAccountAgeWitness.class,
//TakerVerifyAndSignContract.class,
TakerPublishFeeTx.class,
SellerAsTakerSignsDepositTx.class,
SellerCreatesDelayedPayoutTx.class,
SellerSignsDelayedPayoutTx.class,
SellerSendDelayedPayoutTxSignatureRequest.class)
.withTimeout(60))
.executeTasks(); .executeTasks();
} }
} }

View file

@ -20,6 +20,7 @@ package bisq.core.trade.protocol;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeManager; import bisq.core.trade.TradeManager;
import bisq.core.trade.handlers.TradeResultHandler;
import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage; import bisq.core.trade.messages.CounterCurrencyTransferStartedMessage;
import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage; import bisq.core.trade.messages.DepositTxAndDelayedPayoutTxMessage;
import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitMultisigRequest;
@ -60,6 +61,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
protected final ProcessModel processModel; protected final ProcessModel processModel;
protected final Trade trade; protected final Trade trade;
private Timer timeoutTimer; private Timer timeoutTimer;
protected TradeResultHandler tradeResultHandler;
protected ErrorMessageHandler errorMessageHandler;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@ -205,8 +208,8 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer); protected abstract void onTradeMessage(TradeMessage message, NodeAddress peer);
public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); public abstract void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress peer);
public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); public abstract void handleSignContractRequest(SignContractRequest request, NodeAddress peer);
// TODO (woodser): update to use fluent for consistency // TODO (woodser): update to use fluent for consistency
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) { public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
@ -348,12 +351,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
protected void startTimeout(long timeoutSec) { protected void startTimeout(long timeoutSec) {
stopTimeout(); stopTimeout();
timeoutTimer = UserThread.runAfter(() -> { timeoutTimer = UserThread.runAfter(() -> {
log.error("Timeout reached. TradeID={}, state={}, timeoutSec={}", log.error("Timeout reached. TradeID={}, state={}, timeoutSec={}", trade.getId(), trade.stateProperty().get(), timeoutSec);
trade.getId(), trade.stateProperty().get(), timeoutSec);
trade.setErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec."); trade.setErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec.");
if (errorMessageHandler != null) errorMessageHandler.handleErrorMessage("Timeout reached. Protocol did not complete in " + timeoutSec + " sec. TradeID=" + trade.getId() + ", state=" + trade.stateProperty().get());
processModel.getTradeManager().requestPersistence(); processModel.getTradeManager().requestPersistence();
cleanup(); cleanup();
}, timeoutSec); }, timeoutSec);

View file

@ -23,10 +23,8 @@ import bisq.core.trade.messages.PaymentAccountPayloadRequest;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.common.handlers.ErrorMessageHandler;
public interface TraderProtocol { public interface TraderProtocol {
public void handleSignContractResponse(SignContractResponse message, NodeAddress peer, ErrorMessageHandler errorMessageHandler); public void handleSignContractResponse(SignContractResponse message, NodeAddress peer);
public void handleDepositResponse(DepositResponse response, NodeAddress peer, ErrorMessageHandler errorMessageHandler); public void handleDepositResponse(DepositResponse response, NodeAddress peer);
public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer, ErrorMessageHandler errorMessageHandler); public void handlePaymentAccountPayloadRequest(PaymentAccountPayloadRequest request, NodeAddress peer);
} }

View file

@ -22,8 +22,9 @@ import bisq.core.trade.MakerTrade;
import bisq.core.trade.TakerTrade; import bisq.core.trade.TakerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.messages.InitMultisigRequest; import bisq.core.trade.messages.InitMultisigRequest;
import bisq.core.trade.protocol.TradeListener;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
@ -50,6 +51,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
private boolean ack1 = false; private boolean ack1 = false;
private boolean ack2 = false; private boolean ack2 = false;
private boolean failed = false;
private static Object lock = new Object(); private static Object lock = new Object();
MoneroWallet multisigWallet; MoneroWallet multisigWallet;
@ -149,37 +151,31 @@ public class ProcessInitMultisigRequest extends TradeTask {
if (peer2Address == null) throw new RuntimeException("Peer2 address is null"); if (peer2Address == null) throw new RuntimeException("Peer2 address is null");
if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null"); if (peer2PubKeyRing == null) throw new RuntimeException("Peer2 pub key ring null");
// send to peer 1 // complete on successful ack messages
sendInitMultisigRequest(peer1Address, peer1PubKeyRing, new SendDirectMessageListener() { TradeListener ackListener = new TradeListener() {
@Override @Override
public void onArrived() { public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), peer1Address, request.getTradeId(), request.getUid()); if (!ackMessage.getSourceMsgClassName().equals(InitMultisigRequest.class.getSimpleName())) return;
ack1 = true; if (ackMessage.isSuccess()) {
if (ack1 && ack2) completeAux(); if (sender.equals(peer1Address)) ack1 = true;
if (sender.equals(peer2Address)) ack2 = true;
if (ack1 && ack2) {
trade.removeListener(this);
completeAux();
} }
@Override } else {
public void onFault(String errorMessage) { if (!failed) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), peer1Address, errorMessage); failed = true;
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); failed(ackMessage.getErrorMessage()); // TODO: (woodser): only fail once? build into task?
failed();
} }
}); }
}
};
trade.addListener(ackListener);
// send to peer 2 // send to peers
sendInitMultisigRequest(peer2Address, peer2PubKeyRing, new SendDirectMessageListener() { sendInitMultisigRequest(peer1Address, peer1PubKeyRing);
@Override sendInitMultisigRequest(peer2Address, peer2PubKeyRing);
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 { } else {
completeAux(); completeAux();
} }
@ -204,9 +200,9 @@ public class ProcessInitMultisigRequest extends TradeTask {
return peers; return peers;
} }
private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing, SendDirectMessageListener listener) { private void sendInitMultisigRequest(NodeAddress recipient, PubKeyRing pubKeyRing) {
// create multisig message with current multisig hex // create request with current multisig hex
InitMultisigRequest request = new InitMultisigRequest( InitMultisigRequest request = new InitMultisigRequest(
processModel.getOffer().getId(), processModel.getOffer().getId(),
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
@ -218,7 +214,18 @@ public class ProcessInitMultisigRequest extends TradeTask {
processModel.getMadeMultisigHex()); processModel.getMadeMultisigHex());
log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient); log.info("Send {} with offerId {} and uid {} to peer {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), recipient);
processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, listener); processModel.getP2PService().sendEncryptedDirectMessage(recipient, pubKeyRing, request, new SendDirectMessageListener() {
@Override
public void onArrived() {
log.info("{} arrived: peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), recipient, request.getTradeId(), request.getUid());
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), request.getUid(), recipient, errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
} }
private void completeAux() { private void completeAux() {

View file

@ -29,7 +29,9 @@ import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils; import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.messages.SignContractResponse; import bisq.core.trade.messages.SignContractResponse;
import bisq.core.trade.protocol.TradeListener;
import bisq.core.trade.protocol.TradingPeer; import bisq.core.trade.protocol.TradingPeer;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress; import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.util.Date; import java.util.Date;
@ -41,6 +43,7 @@ public class ProcessSignContractRequest extends TradeTask {
private boolean ack1 = false; private boolean ack1 = false;
private boolean ack2 = false; private boolean ack2 = false;
private boolean failed = false;
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public ProcessSignContractRequest(TaskRunner taskHandler, Trade trade) { public ProcessSignContractRequest(TaskRunner taskHandler, Trade trade) {
@ -61,10 +64,13 @@ public class ProcessSignContractRequest extends TradeTask {
trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash()); trader.setPaymentAccountPayloadHash(request.getPaymentAccountPayloadHash());
trader.setPayoutAddressString(request.getPayoutAddress()); trader.setPayoutAddressString(request.getPayoutAddress());
// return contract signature when ready // sign contract only when both deposit txs hashes known
// TODO (woodser): synchronize contract creation; both requests received at the same time // TODO (woodser): synchronize contract creation; both requests received at the same time
// TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade // TODO (woodser): remove makerDepositTxId and takerDepositTxId from Trade
if (processModel.getMaker().getDepositTxHash() != null && processModel.getTaker().getDepositTxHash() != null) { // TODO (woodser): synchronize on process model before setting hash so response only sent once if (processModel.getMaker().getDepositTxHash() == null || processModel.getTaker().getDepositTxHash() == null) {
complete();
return;
}
// create and sign contract // create and sign contract
Contract contract = TradeUtils.createContract(trade); Contract contract = TradeUtils.createContract(trade);
@ -76,6 +82,44 @@ public class ProcessSignContractRequest extends TradeTask {
trade.setContractAsJson(contractAsJson); trade.setContractAsJson(contractAsJson);
trade.getSelf().setContractSignature(signature); trade.getSelf().setContractSignature(signature);
// get response recipients. only arbitrator sends response to both peers
NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMakerNodeAddress() : trade.getTradingPeerNodeAddress();
PubKeyRing recipient1PubKey = trade instanceof ArbitratorTrade ? trade.getMakerPubKeyRing() : trade.getTradingPeerPubKeyRing();
NodeAddress recipient2 = trade instanceof ArbitratorTrade ? trade.getTakerNodeAddress() : null;
PubKeyRing recipient2PubKey = trade instanceof ArbitratorTrade ? trade.getTakerPubKeyRing() : null;
// complete on successful ack messages
TradeListener ackListener = new TradeListener() {
@Override
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
if (!ackMessage.getSourceMsgClassName().equals(SignContractResponse.class.getSimpleName())) return;
if (ackMessage.isSuccess()) {
if (sender.equals(trade.getTradingPeerNodeAddress())) ack1 = true;
if (sender.equals(trade.getArbitratorNodeAddress())) ack2 = true;
if (trade instanceof ArbitratorTrade ? ack1 && ack2 : ack1) { // only arbitrator sends response to both peers
trade.removeListener(this);
complete();
}
} else {
if (!failed) {
failed = true;
failed(ackMessage.getErrorMessage()); // TODO: (woodser): only fail once? build into task?
}
}
}
};
trade.addListener(ackListener);
// send contract signature response(s)
if (recipient1 != null) sendSignContractResponse(recipient1, recipient1PubKey, signature);
if (recipient2 != null) sendSignContractResponse(recipient2, recipient2PubKey, signature);
} catch (Throwable t) {
failed(t);
}
}
private void sendSignContractResponse(NodeAddress nodeAddress, PubKeyRing pubKeyRing, String contractSignature) {
// create response with contract signature // create response with contract signature
SignContractResponse response = new SignContractResponse( SignContractResponse response = new SignContractResponse(
trade.getOffer().getId(), trade.getOffer().getId(),
@ -84,50 +128,20 @@ public class ProcessSignContractRequest extends TradeTask {
UUID.randomUUID().toString(), UUID.randomUUID().toString(),
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
new Date().getTime(), new Date().getTime(),
signature); contractSignature);
// get response recipients. only arbitrator sends response to both peers // send request
NodeAddress recipient1 = trade instanceof ArbitratorTrade ? trade.getMakerNodeAddress() : trade.getTradingPeerNodeAddress(); processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, response, new SendDirectMessageListener() {
PubKeyRing recipient1PubKey = trade instanceof ArbitratorTrade ? trade.getMakerPubKeyRing() : trade.getTradingPeerPubKeyRing();
NodeAddress recipient2 = trade instanceof ArbitratorTrade ? trade.getTakerNodeAddress() : null;
PubKeyRing recipient2PubKey = trade instanceof ArbitratorTrade ? trade.getTakerPubKeyRing() : null;
// send response to recipient 1
processModel.getP2PService().sendEncryptedDirectMessage(recipient1, recipient1PubKey, response, new SendDirectMessageListener() {
@Override @Override
public void onArrived() { public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), recipient1, trade.getId()); log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), nodeAddress, trade.getId());
ack1 = true;
if (ack1 && (recipient2 == null || ack2)) complete();
} }
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), recipient1, trade.getId(), errorMessage); log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage); appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
failed(); 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) complete();
}
@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);
}
} }
} }

View file

@ -18,6 +18,7 @@
package bisq.core.trade.protocol.tasks; package bisq.core.trade.protocol.tasks;
import bisq.common.app.Version; import bisq.common.app.Version;
import bisq.common.crypto.PubKeyRing;
import bisq.common.taskrunner.TaskRunner; import bisq.common.taskrunner.TaskRunner;
import bisq.core.btc.model.XmrAddressEntry; import bisq.core.btc.model.XmrAddressEntry;
import bisq.core.offer.Offer; import bisq.core.offer.Offer;
@ -26,7 +27,10 @@ import bisq.core.trade.SellerTrade;
import bisq.core.trade.Trade; import bisq.core.trade.Trade;
import bisq.core.trade.TradeUtils; import bisq.core.trade.TradeUtils;
import bisq.core.trade.messages.SignContractRequest; import bisq.core.trade.messages.SignContractRequest;
import bisq.core.trade.protocol.TradeListener;
import bisq.core.util.ParsingUtils; import bisq.core.util.ParsingUtils;
import bisq.network.p2p.AckMessage;
import bisq.network.p2p.NodeAddress;
import bisq.network.p2p.SendDirectMessageListener; import bisq.network.p2p.SendDirectMessageListener;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Date; import java.util.Date;
@ -42,6 +46,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
private boolean ack1 = false; private boolean ack1 = false;
private boolean ack2 = false; private boolean ack2 = false;
private boolean failed = false;
@SuppressWarnings({"unused"}) @SuppressWarnings({"unused"})
public SendSignContractRequestAfterMultisig(TaskRunner taskHandler, Trade trade) { public SendSignContractRequestAfterMultisig(TaskRunner taskHandler, Trade trade) {
@ -54,7 +59,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
runInterceptHook(); runInterceptHook();
// skip if multisig wallet not complete // skip if multisig wallet not complete
if (!processModel.isMultisigSetupComplete()) return; if (!processModel.isMultisigSetupComplete()) return; // TODO: woodser: this does not ack original request?
// skip if deposit tx already created // skip if deposit tx already created
if (processModel.getDepositTxXmr() != null) return; if (processModel.getDepositTxXmr() != null) return;
@ -74,7 +79,7 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
MoneroTxWallet depositTx = TradeUtils.createDepositTx(trade.getXmrWalletService(), tradeFee, multisigAddress, depositAmount); MoneroTxWallet depositTx = TradeUtils.createDepositTx(trade.getXmrWalletService(), tradeFee, multisigAddress, depositAmount);
// freeze deposit outputs // freeze deposit outputs
// TODO (woodser): save frozen key images and unfreeze if trade fails before sent to multisig // TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
for (MoneroOutput input : depositTx.getInputs()) { for (MoneroOutput input : depositTx.getInputs()) {
wallet.freezeOutput(input.getKeyImage().getHex()); wallet.freezeOutput(input.getKeyImage().getHex());
} }
@ -83,12 +88,44 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
processModel.setDepositTxXmr(depositTx); processModel.setDepositTxXmr(depositTx);
trade.getSelf().setDepositTxHash(depositTx.getHash()); trade.getSelf().setDepositTxHash(depositTx.getHash());
// create request for peer and arbitrator to sign contract // complete on successful ack messages
TradeListener ackListener = new TradeListener() {
@Override
public void onAckMessage(AckMessage ackMessage, NodeAddress sender) {
if (!ackMessage.getSourceMsgClassName().equals(SignContractRequest.class.getSimpleName())) return;
if (ackMessage.isSuccess()) {
if (sender.equals(trade.getTradingPeerNodeAddress())) ack1 = true;
if (sender.equals(trade.getArbitratorNodeAddress())) ack2 = true;
if (ack1 && ack2) {
trade.removeListener(this);
completeAux();
}
} else {
if (!failed) {
failed = true;
failed(ackMessage.getErrorMessage()); // TODO: (woodser): only fail once? build into task?
}
}
}
};
trade.addListener(ackListener);
// send sign contract requests to peer and arbitrator
sendSignContractRequest(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), offer, depositTx);
sendSignContractRequest(trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), offer, depositTx);
} catch (Throwable t) {
failed(t);
}
}
private void sendSignContractRequest(NodeAddress nodeAddress, PubKeyRing pubKeyRing, Offer offer, MoneroTxWallet depositTx) {
// create request to sign contract
SignContractRequest request = new SignContractRequest( SignContractRequest request = new SignContractRequest(
trade.getOffer().getId(), trade.getOffer().getId(),
processModel.getMyNodeAddress(), processModel.getMyNodeAddress(),
processModel.getPubKeyRing(), processModel.getPubKeyRing(),
UUID.randomUUID().toString(), UUID.randomUUID().toString(), // TODO: ensure not reusing request id across protocol
Version.getP2PMessageVersion(), Version.getP2PMessageVersion(),
new Date().getTime(), new Date().getTime(),
trade.getProcessModel().getAccountId(), trade.getProcessModel().getAccountId(),
@ -96,39 +133,23 @@ public class SendSignContractRequestAfterMultisig extends TradeTask {
trade.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(), trade.getXmrWalletService().getAddressEntry(offer.getId(), XmrAddressEntry.Context.TRADE_PAYOUT).get().getAddressString(),
depositTx.getHash()); depositTx.getHash());
// send request to trading peer // send request
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeerNodeAddress(), trade.getTradingPeerPubKeyRing(), request, new SendDirectMessageListener() { processModel.getP2PService().sendEncryptedDirectMessage(nodeAddress, pubKeyRing, request, new SendDirectMessageListener() {
@Override @Override
public void onArrived() { public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId()); log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), nodeAddress, trade.getId());
ack1 = true;
if (ack1 && ack2) complete();
} }
@Override @Override
public void onFault(String errorMessage) { public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getTradingPeerNodeAddress(), trade.getId(), errorMessage); log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), nodeAddress, trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage); appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed(); failed();
} }
}); });
}
// send request to arbitrator private void completeAux() {
processModel.getP2PService().sendEncryptedDirectMessage(trade.getArbitratorNodeAddress(), trade.getArbitratorPubKeyRing(), request, new SendDirectMessageListener() { processModel.getXmrWalletService().getWallet().save();
@Override complete();
public void onArrived() {
log.info("{} arrived: trading peer={}; offerId={}; uid={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId());
ack2 = true;
if (ack1 && ack2) complete();
}
@Override
public void onFault(String errorMessage) {
log.error("Sending {} failed: uid={}; peer={}; error={}", request.getClass().getSimpleName(), trade.getArbitratorNodeAddress(), trade.getId(), errorMessage);
appendToErrorMessage("Sending message failed: message=" + request + "\nerrorMessage=" + errorMessage);
failed();
}
});
} catch (Throwable t) {
failed(t);
}
} }
} }

View file

@ -27,6 +27,8 @@ import bisq.proto.grpc.ConfirmPaymentStartedReply;
import bisq.proto.grpc.ConfirmPaymentStartedRequest; import bisq.proto.grpc.ConfirmPaymentStartedRequest;
import bisq.proto.grpc.GetTradeReply; import bisq.proto.grpc.GetTradeReply;
import bisq.proto.grpc.GetTradeRequest; import bisq.proto.grpc.GetTradeRequest;
import bisq.proto.grpc.GetTradesReply;
import bisq.proto.grpc.GetTradesRequest;
import bisq.proto.grpc.KeepFundsReply; import bisq.proto.grpc.KeepFundsReply;
import bisq.proto.grpc.KeepFundsRequest; import bisq.proto.grpc.KeepFundsRequest;
import bisq.proto.grpc.TakeOfferReply; import bisq.proto.grpc.TakeOfferReply;
@ -40,8 +42,9 @@ import io.grpc.stub.StreamObserver;
import javax.inject.Inject; import javax.inject.Inject;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.stream.Collectors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static bisq.core.api.model.TradeInfo.toTradeInfo; import static bisq.core.api.model.TradeInfo.toTradeInfo;
@ -87,6 +90,25 @@ class GrpcTradesService extends TradesImplBase {
} }
} }
@Override
public void getTrades(GetTradesRequest req,
StreamObserver<GetTradesReply> responseObserver) {
try {
List<TradeInfo> trades = coreApi.getTrades()
.stream().map(TradeInfo::toTradeInfo)
.collect(Collectors.toList());
var reply = GetTradesReply.newBuilder()
.addAllTrades(trades.stream()
.map(TradeInfo::toProtoMessage)
.collect(Collectors.toList()))
.build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
} catch (Throwable cause) {
exceptionHandler.handleException(log, cause, responseObserver);
}
}
@Override @Override
public void takeOffer(TakeOfferRequest req, public void takeOffer(TakeOfferRequest req,
StreamObserver<TakeOfferReply> responseObserver) { StreamObserver<TakeOfferReply> responseObserver) {
@ -173,8 +195,9 @@ class GrpcTradesService extends TradesImplBase {
return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass()) return getCustomRateMeteringInterceptor(coreApi.getConfig().appDataDir, this.getClass())
.or(() -> Optional.of(CallRateMeteringInterceptor.valueOf( .or(() -> Optional.of(CallRateMeteringInterceptor.valueOf(
new HashMap<>() {{ new HashMap<>() {{
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(3, SECONDS)); put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS)); put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
put(getKeepFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES)); put(getKeepFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));

View file

@ -300,6 +300,8 @@ message StopReply {
service Trades { service Trades {
rpc GetTrade (GetTradeRequest) returns (GetTradeReply) { rpc GetTrade (GetTradeRequest) returns (GetTradeReply) {
} }
rpc GetTrades (GetTradesRequest) returns (GetTradesReply) {
}
rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) { rpc TakeOffer (TakeOfferRequest) returns (TakeOfferReply) {
} }
rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) { rpc ConfirmPaymentStarted (ConfirmPaymentStartedRequest) returns (ConfirmPaymentStartedReply) {
@ -344,6 +346,13 @@ message GetTradeReply {
TradeInfo trade = 1; TradeInfo trade = 1;
} }
message GetTradesRequest {
}
message GetTradesReply {
repeated TradeInfo trades = 1;
}
message KeepFundsRequest { message KeepFundsRequest {
string trade_id = 1; string trade_id = 1;
} }