/* * This file is part of Bitsquare. * * Bitsquare is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or (at * your option) any later version. * * Bitsquare is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public * License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Bitsquare. If not, see <http://www.gnu.org/licenses/>. */ package io.bitsquare.trade.protocol.trade; import io.bitsquare.common.Timer; import io.bitsquare.common.UserThread; import io.bitsquare.common.crypto.PubKeyRing; import io.bitsquare.crypto.DecryptedMsgWithPubKey; import io.bitsquare.p2p.Message; import io.bitsquare.p2p.NodeAddress; import io.bitsquare.p2p.messaging.DecryptedDirectMessageListener; import io.bitsquare.trade.OffererTrade; import io.bitsquare.trade.Trade; import io.bitsquare.trade.TradeManager; import io.bitsquare.trade.protocol.trade.messages.TradeMessage; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.security.PublicKey; import static io.bitsquare.util.Validator.nonEmptyStringOf; public abstract class TradeProtocol { private static final Logger log = LoggerFactory.getLogger(TradeProtocol.class); private static final long TIMEOUT_SEC = 75; protected final ProcessModel processModel; private final DecryptedDirectMessageListener decryptedDirectMessageListener; protected Trade trade; private Timer timeoutTimer; public TradeProtocol(Trade trade) { this.trade = trade; this.processModel = trade.getProcessModel(); decryptedDirectMessageListener = (decryptedMessageWithPubKey, peersNodeAddress) -> { // We check the sig only as soon we have stored the peers pubKeyRing. PubKeyRing tradingPeerPubKeyRing = processModel.tradingPeer.getPubKeyRing(); PublicKey signaturePubKey = decryptedMessageWithPubKey.signaturePubKey; if (tradingPeerPubKeyRing != null && signaturePubKey.equals(tradingPeerPubKeyRing.getSignaturePubKey())) { Message message = decryptedMessageWithPubKey.message; log.trace("handleNewMessage: message = " + message.getClass().getSimpleName() + " from " + peersNodeAddress); if (message instanceof TradeMessage) { TradeMessage tradeMessage = (TradeMessage) message; nonEmptyStringOf(tradeMessage.tradeId); if (tradeMessage.tradeId.equals(processModel.getId())) doHandleDecryptedMessage(tradeMessage, peersNodeAddress); } } //else { //TODO not clear anymore what case is handled here // it might be that we received a msg from the arbitrator, we don't handle that here but we don't want to log an error /*Optional<Arbitrator> arbitratorOptional = processModel.getArbitratorManager().getArbitratorsObservableMap().values().stream() .filter(e -> e.getArbitratorAddress().equals(trade.getArbitratorAddress())).findFirst(); PubKeyRing arbitratorPubKeyRing = null; if (arbitratorOptional.isPresent()) arbitratorPubKeyRing = arbitratorOptional.get().getPubKeyRing(); if ((arbitratorPubKeyRing != null && !signaturePubKey.equals(arbitratorPubKeyRing.getSignaturePubKey()))) log.error("Signature used in seal message does not match the one stored with that trade for the trading peer or arbitrator.");*/ //} }; processModel.getP2PService().addDecryptedDirectMessageListener(decryptedDirectMessageListener); } public void completed() { cleanup(); // We only removed earlier the listner here, but then we migth have dangling trades after faults... // so lets remove it at cleanup //processModel.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener); } private void cleanup() { log.debug("cleanup " + this); stopTimeout(); // We removed that from here earlier as it broke the trade process in some non critical error cases. // But it should be actually removed... processModel.getP2PService().removeDecryptedDirectMessageListener(decryptedDirectMessageListener); } public void applyMailboxMessage(DecryptedMsgWithPubKey decryptedMsgWithPubKey, Trade trade) { log.debug("applyMailboxMessage " + decryptedMsgWithPubKey.message); if (decryptedMsgWithPubKey.signaturePubKey.equals(processModel.tradingPeer.getPubKeyRing().getSignaturePubKey())) doApplyMailboxMessage(decryptedMsgWithPubKey.message, trade); else log.error("SignaturePubKey in message does not match the SignaturePubKey we have stored to that trading peer."); } protected abstract void doApplyMailboxMessage(Message message, Trade trade); protected abstract void doHandleDecryptedMessage(TradeMessage tradeMessage, NodeAddress peerNodeAddress); protected void startTimeout() { stopTimeout(); timeoutTimer = UserThread.runAfter(() -> { log.error("Timeout reached. TradeID=" + trade.getId()); trade.setErrorMessage("A timeout occurred."); cleanupTradable(); cleanup(); }, TIMEOUT_SEC); } protected void stopTimeout() { if (timeoutTimer != null) { timeoutTimer.stop(); timeoutTimer = null; } } protected void handleTaskRunnerSuccess(String info) { log.debug("handleTaskRunnerSuccess " + info); } protected void handleTaskRunnerFault(String errorMessage) { log.error(errorMessage); cleanupTradable(); cleanup(); } private void cleanupTradable() { Trade.State tradeState = trade.getState(); log.debug("cleanupTradable tradeState=" + tradeState); boolean isOffererTrade = trade instanceof OffererTrade; if (isOffererTrade && (tradeState == Trade.State.OFFERER_SENT_PUBLISH_DEPOSIT_TX_REQUEST || tradeState == Trade.State.DEPOSIT_SEEN_IN_NETWORK)) processModel.getOpenOfferManager().closeOpenOffer(trade.getOffer()); //boolean isTakerTrade = trade instanceof TakerTrade; // if (isTakerTrade) { TradeManager tradeManager = processModel.getTradeManager(); if (tradeState.getPhase() == Trade.Phase.PREPARATION) { tradeManager.removePreparedTrade(trade); } else if (tradeState.getPhase() == Trade.Phase.TAKER_FEE_PAID) { tradeManager.addTradeToFailedTrades(trade); processModel.getWalletService().swapAnyTradeEntryContextToAvailableEntry(trade.getId()); } // } } }