mirror of
https://github.com/haveno-dex/haveno.git
synced 2024-11-16 15:58:08 +00:00
refactor payout protocol
send payment key & multisig hex on deposit confirm for resilience support payout published, confirmed, unlocked states keep trade wallets open throughout trade close and delete trade wallets when payout unlocks arbitrator idles trade wallets after deposits confirm (1/hour)
This commit is contained in:
parent
45bac8c264
commit
f36dde2857
84 changed files with 1486 additions and 2272 deletions
|
@ -16,7 +16,7 @@ import org.junit.jupiter.api.TestInfo;
|
||||||
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
import static bisq.cli.table.builder.TableType.TRADE_DETAIL_TBL;
|
||||||
import static bisq.core.trade.Trade.Phase.DEPOSITS_UNLOCKED;
|
import static bisq.core.trade.Trade.Phase.DEPOSITS_UNLOCKED;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG;
|
import static bisq.core.trade.Trade.State.BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG;
|
||||||
import static bisq.core.trade.Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN;
|
import static bisq.core.trade.Trade.State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG;
|
import static bisq.core.trade.Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG;
|
||||||
|
@ -150,7 +150,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
String tradeId) {
|
String tradeId) {
|
||||||
Predicate<TradeInfo> isTradeInPaymentReceiptConfirmedStateAndPhase = (t) ->
|
Predicate<TradeInfo> isTradeInPaymentReceiptConfirmedStateAndPhase = (t) ->
|
||||||
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name()) &&
|
t.getState().equals(SELLER_RECEIVED_PAYMENT_SENT_MSG.name()) &&
|
||||||
(t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(PAYMENT_SENT.name()));
|
t.getPhase().equals(PAYMENT_SENT.name());
|
||||||
String userName = toUserName.apply(grpcClient);
|
String userName = toUserName.apply(grpcClient);
|
||||||
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
TradeInfo trade = grpcClient.getTrade(tradeId);
|
TradeInfo trade = grpcClient.getTrade(tradeId);
|
||||||
|
|
|
@ -30,13 +30,10 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInfo;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.OfferDirection.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
import static protobuf.OpenOffer.State.AVAILABLE;
|
import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
|
@ -113,8 +110,8 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
trade = bobClient.getTrade(tradeId);
|
trade = bobClient.getTrade(tradeId);
|
||||||
// Note: offer.state == available
|
// Note: offer.state == available
|
||||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYMENT_RECEIVED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setPaymentReceivedMessageSent(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
|
@ -50,8 +50,8 @@ import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG;
|
||||||
import static org.junit.jupiter.api.Assertions.*;
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||||
import static protobuf.OfferDirection.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
@ -200,8 +200,8 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
|
||||||
trade = bobClient.getTrade(tradeId);
|
trade = bobClient.getTrade(tradeId);
|
||||||
// Note: offer.state == available
|
// Note: offer.state == available
|
||||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYMENT_RECEIVED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setPaymentReceivedMessageSent(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
|
@ -29,11 +29,10 @@ import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.TestInfo;
|
import org.junit.jupiter.api.TestInfo;
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
import static protobuf.Offer.State.OFFER_FEE_RESERVED;
|
||||||
|
@ -130,8 +129,8 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
trade = aliceClient.getTrade(tradeId);
|
trade = aliceClient.getTrade(tradeId);
|
||||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYMENT_RECEIVED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setPaymentReceivedMessageSent(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
|
@ -32,10 +32,10 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.apitest.config.ApiTestConfig.USD;
|
import static bisq.apitest.config.ApiTestConfig.USD;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
import static bisq.core.trade.Trade.Phase.COMPLETED;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG;
|
||||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
import static bisq.core.trade.Trade.State.TRADE_COMPLETED;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
@ -119,8 +119,8 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
sleep(3_000);
|
sleep(3_000);
|
||||||
trade = aliceClient.getTrade(tradeId);
|
trade = aliceClient.getTrade(tradeId);
|
||||||
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
assertEquals(OFFER_FEE_RESERVED.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYMENT_RECEIVED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setPaymentReceivedMessageSent(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
|
@ -32,12 +32,9 @@ import org.junit.jupiter.api.TestMethodOrder;
|
||||||
import static bisq.apitest.config.ApiTestConfig.BTC;
|
import static bisq.apitest.config.ApiTestConfig.BTC;
|
||||||
import static bisq.apitest.config.ApiTestConfig.XMR;
|
import static bisq.apitest.config.ApiTestConfig.XMR;
|
||||||
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
import static bisq.cli.table.builder.TableType.OFFER_TBL;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYMENT_RECEIVED;
|
||||||
import static bisq.core.trade.Trade.Phase.WITHDRAWN;
|
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG;
|
||||||
import static bisq.core.trade.Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG;
|
|
||||||
import static bisq.core.trade.Trade.State.WITHDRAW_COMPLETED;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
import static protobuf.OfferDirection.BUY;
|
import static protobuf.OfferDirection.BUY;
|
||||||
|
|
||||||
|
@ -139,8 +136,8 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
trade = bobClient.getTrade(tradeId);
|
trade = bobClient.getTrade(tradeId);
|
||||||
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_RESERVED.
|
// Warning: trade.getOffer().getState() might be AVAILABLE, not OFFER_FEE_RESERVED.
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG)
|
||||||
.setPhase(PAYOUT_PUBLISHED)
|
.setPhase(PAYMENT_RECEIVED)
|
||||||
.setPayoutPublished(true)
|
.setPayoutPublished(true)
|
||||||
.setPaymentReceivedMessageSent(true);
|
.setPaymentReceivedMessageSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
|
@ -105,9 +105,6 @@ public class CoreDisputesService {
|
||||||
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
||||||
disputeManager.sendOpenNewDisputeMessage(dispute, false, updatedMultisigHex, resultHandler, faultHandler);
|
disputeManager.sendOpenNewDisputeMessage(dispute, false, updatedMultisigHex, resultHandler, faultHandler);
|
||||||
tradeManager.requestPersistence();
|
tradeManager.requestPersistence();
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
xmrWalletService.closeMultisigWallet(trade.getId());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,7 +156,8 @@ public class CoreDisputesService {
|
||||||
if (disputeOptional.isPresent()) dispute = disputeOptional.get();
|
if (disputeOptional.isPresent()) dispute = disputeOptional.get();
|
||||||
else throw new IllegalStateException(format("dispute for tradeId '%s' not found", tradeId));
|
else throw new IllegalStateException(format("dispute for tradeId '%s' not found", tradeId));
|
||||||
|
|
||||||
synchronized (tradeManager.getTrade(tradeId)) {
|
Trade trade = tradeManager.getTrade(tradeId);
|
||||||
|
synchronized (trade) {
|
||||||
var closeDate = new Date();
|
var closeDate = new Date();
|
||||||
var disputeResult = createDisputeResult(dispute, winner, reason, summaryNotes, closeDate);
|
var disputeResult = createDisputeResult(dispute, winner, reason, summaryNotes, closeDate);
|
||||||
var contract = dispute.getContract();
|
var contract = dispute.getContract();
|
||||||
|
@ -176,8 +174,8 @@ public class CoreDisputesService {
|
||||||
}
|
}
|
||||||
applyPayoutAmountsToDisputeResult(payout, dispute, disputeResult, customWinnerAmount);
|
applyPayoutAmountsToDisputeResult(payout, dispute, disputeResult, customWinnerAmount);
|
||||||
|
|
||||||
// resolve the payout
|
// apply dispute payout
|
||||||
resolveDisputePayout(dispute, disputeResult, contract);
|
applyDisputePayout(dispute, disputeResult, contract);
|
||||||
|
|
||||||
// close dispute ticket
|
// close dispute ticket
|
||||||
closeDispute(arbitrationManager, dispute, disputeResult, false);
|
closeDispute(arbitrationManager, dispute, disputeResult, false);
|
||||||
|
@ -186,19 +184,17 @@ public class CoreDisputesService {
|
||||||
var peersDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream()
|
var peersDisputeOptional = arbitrationManager.getDisputesAsObservableList().stream()
|
||||||
.filter(d -> tradeId.equals(d.getTradeId()) && dispute.getTraderId() != d.getTraderId())
|
.filter(d -> tradeId.equals(d.getTradeId()) && dispute.getTraderId() != d.getTraderId())
|
||||||
.findFirst();
|
.findFirst();
|
||||||
|
|
||||||
if (peersDisputeOptional.isPresent()) {
|
if (peersDisputeOptional.isPresent()) {
|
||||||
var peerDispute = peersDisputeOptional.get();
|
var peerDispute = peersDisputeOptional.get();
|
||||||
var peerDisputeResult = createDisputeResult(peerDispute, winner, reason, summaryNotes, closeDate);
|
var peerDisputeResult = createDisputeResult(peerDispute, winner, reason, summaryNotes, closeDate);
|
||||||
peerDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
peerDisputeResult.setBuyerPayoutAmount(disputeResult.getBuyerPayoutAmount());
|
||||||
peerDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
peerDisputeResult.setSellerPayoutAmount(disputeResult.getSellerPayoutAmount());
|
||||||
peerDisputeResult.setLoserPublisher(disputeResult.isLoserPublisher());
|
peerDisputeResult.setLoserPublisher(disputeResult.isLoserPublisher());
|
||||||
resolveDisputePayout(peerDispute, peerDisputeResult, peerDispute.getContract());
|
applyDisputePayout(peerDispute, peerDisputeResult, peerDispute.getContract());
|
||||||
closeDispute(arbitrationManager, peerDispute, peerDisputeResult, false);
|
closeDispute(arbitrationManager, peerDispute, peerDisputeResult, false);
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("could not find peer dispute");
|
throw new IllegalStateException("could not find peer dispute");
|
||||||
}
|
}
|
||||||
|
|
||||||
arbitrationManager.requestPersistence();
|
arbitrationManager.requestPersistence();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -250,7 +246,7 @@ public class CoreDisputesService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void resolveDisputePayout(Dispute dispute, DisputeResult disputeResult, Contract contract) {
|
public void applyDisputePayout(Dispute dispute, DisputeResult disputeResult, Contract contract) {
|
||||||
// TODO (woodser): create disputed payout tx after showing payout tx confirmation, within doCloseIfValid() (see upstream/master)
|
// TODO (woodser): create disputed payout tx after showing payout tx confirmation, within doCloseIfValid() (see upstream/master)
|
||||||
if (!dispute.isMediationDispute()) {
|
if (!dispute.isMediationDispute()) {
|
||||||
try {
|
try {
|
||||||
|
@ -259,30 +255,25 @@ public class CoreDisputesService {
|
||||||
//dispute.getContract().getArbitratorPubKeyRing(); // TODO: support arbitrator pub key ring in contract?
|
//dispute.getContract().getArbitratorPubKeyRing(); // TODO: support arbitrator pub key ring in contract?
|
||||||
//disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey());
|
//disputeResult.setArbitratorPubKey(arbitratorAddressEntry.getPubKey());
|
||||||
|
|
||||||
// TODO (woodser): don't send signed tx if opener is not co-signer?
|
// determine if dispute is in context of publisher
|
||||||
// // determine if opener is co-signer
|
boolean isOpener = dispute.isOpener();
|
||||||
// boolean openerIsWinner = (contract.getBuyerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.BUYER) || (contract.getSellerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == Winner.SELLER);
|
boolean isWinner = (contract.getBuyerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == DisputeResult.Winner.BUYER) || (contract.getSellerPubKeyRing().equals(dispute.getTraderPubKeyRing()) && disputeResult.getWinner() == DisputeResult.Winner.SELLER);
|
||||||
// boolean openerIsCosigner = openerIsWinner || disputeResult.isLoserPublisher();
|
boolean isPublisher = disputeResult.isLoserPublisher() ? !isWinner : isWinner;
|
||||||
// if (!openerIsCosigner) throw new RuntimeException("Need to query non-opener for updated multisig hex before creating tx");
|
|
||||||
|
|
||||||
// open multisig wallet
|
// open multisig wallet
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
||||||
|
|
||||||
// arbitrator creates and signs dispute payout tx if dispute is in context of opener, otherwise opener's peer must request payout tx by providing updated multisig hex
|
// if dispute is in context of opener, arbitrator has multisig hex to create and validate payout tx
|
||||||
boolean isOpener = dispute.isOpener();
|
|
||||||
System.out.println("Is dispute opener: " + isOpener);
|
|
||||||
if (isOpener) {
|
if (isOpener) {
|
||||||
MoneroTxWallet arbitratorPayoutTx = ArbitrationManager.arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
|
MoneroTxWallet arbitratorPayoutTx = ArbitrationManager.arbitratorCreatesDisputedPayoutTx(contract, dispute, disputeResult, multisigWallet);
|
||||||
System.out.println("Created arbitrator-signed payout tx: " + arbitratorPayoutTx);
|
System.out.println("Created arbitrator-signed payout tx: " + arbitratorPayoutTx);
|
||||||
if (arbitratorPayoutTx != null)
|
|
||||||
disputeResult.setArbitratorSignedPayoutTxHex(arbitratorPayoutTx.getTxSet().getMultisigTxHex());
|
// if opener is publisher, include signed payout tx in dispute result, otherwise publisher must request payout tx by providing updated multisig hex
|
||||||
|
if (isPublisher) disputeResult.setArbitratorSignedPayoutTxHex(arbitratorPayoutTx.getTxSet().getMultisigTxHex());
|
||||||
}
|
}
|
||||||
|
|
||||||
// send arbitrator's updated multisig hex with dispute result
|
// send arbitrator's updated multisig hex with dispute result
|
||||||
disputeResult.setArbitratorUpdatedMultisigHex(multisigWallet.exportMultisigHex());
|
disputeResult.setArbitratorUpdatedMultisigHex(multisigWallet.exportMultisigHex());
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
xmrWalletService.closeMultisigWallet(dispute.getTradeId());
|
|
||||||
}
|
}
|
||||||
} catch (AddressFormatException e2) {
|
} catch (AddressFormatException e2) {
|
||||||
log.error("Error at close dispute", e2);
|
log.error("Error at close dispute", e2);
|
||||||
|
|
|
@ -5,7 +5,7 @@ import bisq.common.config.Config;
|
||||||
import bisq.core.btc.model.EncryptedConnectionList;
|
import bisq.core.btc.model.EncryptedConnectionList;
|
||||||
import bisq.core.btc.setup.DownloadListener;
|
import bisq.core.btc.setup.DownloadListener;
|
||||||
import bisq.core.btc.setup.WalletsSetup;
|
import bisq.core.btc.setup.WalletsSetup;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -239,7 +239,7 @@ public final class CoreMoneroConnectionsService {
|
||||||
public long getDefaultRefreshPeriodMs() {
|
public long getDefaultRefreshPeriodMs() {
|
||||||
if (daemon == null) return REFRESH_PERIOD_LOCAL_MS;
|
if (daemon == null) return REFRESH_PERIOD_LOCAL_MS;
|
||||||
else {
|
else {
|
||||||
boolean isLocal = TradeUtils.isLocalHost(daemon.getRpcConnection().getUri());
|
boolean isLocal = HavenoUtils.isLocalHost(daemon.getRpcConnection().getUri());
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
updateDaemonInfo();
|
updateDaemonInfo();
|
||||||
if (lastInfo != null && (lastInfo.isBusySyncing() || (lastInfo.getHeightWithoutBootstrap() != null && lastInfo.getHeightWithoutBootstrap() > 0 && lastInfo.getHeightWithoutBootstrap() < lastInfo.getHeight()))) return REFRESH_PERIOD_REMOTE_MS; // refresh slower if syncing or bootstrapped
|
if (lastInfo != null && (lastInfo.isBusySyncing() || (lastInfo.getHeightWithoutBootstrap() != null && lastInfo.getHeightWithoutBootstrap() > 0 && lastInfo.getHeightWithoutBootstrap() < lastInfo.getHeight()))) return REFRESH_PERIOD_REMOTE_MS; // refresh slower if syncing or bootstrapped
|
||||||
|
@ -363,7 +363,7 @@ public final class CoreMoneroConnectionsService {
|
||||||
// if offline and last connection is local, start local node if offline
|
// if offline and last connection is local, start local node if offline
|
||||||
currentConnectionUri.ifPresent(uri -> {
|
currentConnectionUri.ifPresent(uri -> {
|
||||||
try {
|
try {
|
||||||
if (!connectionManager.isConnected() && TradeUtils.isLocalHost(uri) && !nodeService.isOnline()) {
|
if (!connectionManager.isConnected() && HavenoUtils.isLocalHost(uri) && !nodeService.isOnline()) {
|
||||||
nodeService.startMoneroNode();
|
nodeService.startMoneroNode();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -372,7 +372,7 @@ public final class CoreMoneroConnectionsService {
|
||||||
});
|
});
|
||||||
|
|
||||||
// prefer to connect to local node unless prevented by configuration
|
// prefer to connect to local node unless prevented by configuration
|
||||||
if (("".equals(config.xmrNode) || TradeUtils.isLocalHost(config.xmrNode)) &&
|
if (("".equals(config.xmrNode) || HavenoUtils.isLocalHost(config.xmrNode)) &&
|
||||||
(!connectionManager.isConnected() || connectionManager.getAutoSwitch()) &&
|
(!connectionManager.isConnected() || connectionManager.getAutoSwitch()) &&
|
||||||
nodeService.isConnected()) {
|
nodeService.isConnected()) {
|
||||||
MoneroRpcConnection connection = connectionManager.getConnectionByUri(nodeService.getDaemon().getRpcConnection().getUri());
|
MoneroRpcConnection connection = connectionManager.getConnectionByUri(nodeService.getDaemon().getRpcConnection().getUri());
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package bisq.core.api;
|
package bisq.core.api;
|
||||||
|
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.xmr.MoneroNodeSettings;
|
import bisq.core.xmr.MoneroNodeSettings;
|
||||||
import bisq.common.config.BaseCurrencyNetwork;
|
import bisq.common.config.BaseCurrencyNetwork;
|
||||||
|
@ -71,7 +71,7 @@ public class CoreMoneroNodeService {
|
||||||
else if (Config.baseCurrencyNetwork().isTestnet()) rpcPort = 28081;
|
else if (Config.baseCurrencyNetwork().isTestnet()) rpcPort = 28081;
|
||||||
else if (Config.baseCurrencyNetwork().isStagenet()) rpcPort = 38081;
|
else if (Config.baseCurrencyNetwork().isStagenet()) rpcPort = 38081;
|
||||||
else throw new RuntimeException("Base network is not local testnet, stagenet, or mainnet");
|
else throw new RuntimeException("Base network is not local testnet, stagenet, or mainnet");
|
||||||
this.daemon = new MoneroDaemonRpc("http://" + TradeUtils.LOOPBACK_HOST + ":" + rpcPort);
|
this.daemon = new MoneroDaemonRpc("http://" + HavenoUtils.LOOPBACK_HOST + ":" + rpcPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(MoneroNodeServiceListener listener) {
|
public void addListener(MoneroNodeServiceListener listener) {
|
||||||
|
|
|
@ -79,12 +79,13 @@ public class TradeInfo implements Payload {
|
||||||
private final String state;
|
private final String state;
|
||||||
private final String phase;
|
private final String phase;
|
||||||
private final String periodState;
|
private final String periodState;
|
||||||
|
private final String payoutState;
|
||||||
private final boolean isDepositPublished;
|
private final boolean isDepositPublished;
|
||||||
private final boolean isDepositUnlocked;
|
private final boolean isDepositUnlocked;
|
||||||
private final boolean isPaymentSent;
|
private final boolean isPaymentSent;
|
||||||
private final boolean isPaymentReceived;
|
private final boolean isPaymentReceived;
|
||||||
private final boolean isPayoutPublished;
|
|
||||||
private final boolean isCompleted;
|
private final boolean isCompleted;
|
||||||
|
private final boolean isPayoutPublished;
|
||||||
private final String contractAsJson;
|
private final String contractAsJson;
|
||||||
private final ContractInfo contract;
|
private final ContractInfo contract;
|
||||||
|
|
||||||
|
@ -107,6 +108,7 @@ public class TradeInfo implements Payload {
|
||||||
this.state = builder.getState();
|
this.state = builder.getState();
|
||||||
this.phase = builder.getPhase();
|
this.phase = builder.getPhase();
|
||||||
this.periodState = builder.getPeriodState();
|
this.periodState = builder.getPeriodState();
|
||||||
|
this.payoutState = builder.getPayoutState();
|
||||||
this.isDepositPublished = builder.isDepositPublished();
|
this.isDepositPublished = builder.isDepositPublished();
|
||||||
this.isDepositUnlocked = builder.isDepositUnlocked();
|
this.isDepositUnlocked = builder.isDepositUnlocked();
|
||||||
this.isPaymentSent = builder.isPaymentSent();
|
this.isPaymentSent = builder.isPaymentSent();
|
||||||
|
@ -158,6 +160,7 @@ public class TradeInfo implements Payload {
|
||||||
.withState(trade.getState().name())
|
.withState(trade.getState().name())
|
||||||
.withPhase(trade.getPhase().name())
|
.withPhase(trade.getPhase().name())
|
||||||
.withPeriodState(trade.getPeriodState().name())
|
.withPeriodState(trade.getPeriodState().name())
|
||||||
|
.withPayoutState(trade.getPayoutState().name())
|
||||||
.withIsDepositPublished(trade.isDepositPublished())
|
.withIsDepositPublished(trade.isDepositPublished())
|
||||||
.withIsDepositUnlocked(trade.isDepositUnlocked())
|
.withIsDepositUnlocked(trade.isDepositUnlocked())
|
||||||
.withIsPaymentSent(trade.isPaymentSent())
|
.withIsPaymentSent(trade.isPaymentSent())
|
||||||
|
@ -195,12 +198,13 @@ public class TradeInfo implements Payload {
|
||||||
.setState(state)
|
.setState(state)
|
||||||
.setPhase(phase)
|
.setPhase(phase)
|
||||||
.setPeriodState(periodState)
|
.setPeriodState(periodState)
|
||||||
|
.setPayoutState(payoutState)
|
||||||
.setIsDepositPublished(isDepositPublished)
|
.setIsDepositPublished(isDepositPublished)
|
||||||
.setIsDepositUnlocked(isDepositUnlocked)
|
.setIsDepositUnlocked(isDepositUnlocked)
|
||||||
.setIsPaymentSent(isPaymentSent)
|
.setIsPaymentSent(isPaymentSent)
|
||||||
.setIsPaymentReceived(isPaymentReceived)
|
.setIsPaymentReceived(isPaymentReceived)
|
||||||
|
.setIsCompleted(isCompleted)
|
||||||
.setIsPayoutPublished(isPayoutPublished)
|
.setIsPayoutPublished(isPayoutPublished)
|
||||||
.setIsPayoutPublished(isCompleted)
|
|
||||||
.setContractAsJson(contractAsJson == null ? "" : contractAsJson)
|
.setContractAsJson(contractAsJson == null ? "" : contractAsJson)
|
||||||
.setContract(contract.toProtoMessage())
|
.setContract(contract.toProtoMessage())
|
||||||
.build();
|
.build();
|
||||||
|
@ -222,6 +226,7 @@ public class TradeInfo implements Payload {
|
||||||
.withPrice(proto.getPrice())
|
.withPrice(proto.getPrice())
|
||||||
.withVolume(proto.getTradeVolume())
|
.withVolume(proto.getTradeVolume())
|
||||||
.withPeriodState(proto.getPeriodState())
|
.withPeriodState(proto.getPeriodState())
|
||||||
|
.withPayoutState(proto.getPayoutState())
|
||||||
.withState(proto.getState())
|
.withState(proto.getState())
|
||||||
.withPhase(proto.getPhase())
|
.withPhase(proto.getPhase())
|
||||||
.withArbitratorNodeAddress(proto.getArbitratorNodeAddress())
|
.withArbitratorNodeAddress(proto.getArbitratorNodeAddress())
|
||||||
|
@ -230,8 +235,8 @@ public class TradeInfo implements Payload {
|
||||||
.withIsDepositUnlocked(proto.getIsDepositUnlocked())
|
.withIsDepositUnlocked(proto.getIsDepositUnlocked())
|
||||||
.withIsPaymentSent(proto.getIsPaymentSent())
|
.withIsPaymentSent(proto.getIsPaymentSent())
|
||||||
.withIsPaymentReceived(proto.getIsPaymentReceived())
|
.withIsPaymentReceived(proto.getIsPaymentReceived())
|
||||||
.withIsPayoutPublished(proto.getIsPayoutPublished())
|
|
||||||
.withIsCompleted(proto.getIsCompleted())
|
.withIsCompleted(proto.getIsCompleted())
|
||||||
|
.withIsPayoutPublished(proto.getIsPayoutPublished())
|
||||||
.withContractAsJson(proto.getContractAsJson())
|
.withContractAsJson(proto.getContractAsJson())
|
||||||
.withContract((ContractInfo.fromProto(proto.getContract())))
|
.withContract((ContractInfo.fromProto(proto.getContract())))
|
||||||
.build();
|
.build();
|
||||||
|
@ -256,12 +261,13 @@ public class TradeInfo implements Payload {
|
||||||
", state='" + state + '\'' + "\n" +
|
", state='" + state + '\'' + "\n" +
|
||||||
", phase='" + phase + '\'' + "\n" +
|
", phase='" + phase + '\'' + "\n" +
|
||||||
", periodState='" + periodState + '\'' + "\n" +
|
", periodState='" + periodState + '\'' + "\n" +
|
||||||
|
", payoutState='" + payoutState + '\'' + "\n" +
|
||||||
", isDepositPublished=" + isDepositPublished + "\n" +
|
", isDepositPublished=" + isDepositPublished + "\n" +
|
||||||
", isDepositConfirmed=" + isDepositUnlocked + "\n" +
|
", isDepositConfirmed=" + isDepositUnlocked + "\n" +
|
||||||
", isPaymentSent=" + isPaymentSent + "\n" +
|
", isPaymentSent=" + isPaymentSent + "\n" +
|
||||||
", isPaymentReceived=" + isPaymentReceived + "\n" +
|
", isPaymentReceived=" + isPaymentReceived + "\n" +
|
||||||
", isPayoutPublished=" + isPayoutPublished + "\n" +
|
|
||||||
", isCompleted=" + isCompleted + "\n" +
|
", isCompleted=" + isCompleted + "\n" +
|
||||||
|
", isPayoutPublished=" + isPayoutPublished + "\n" +
|
||||||
", offer=" + offer + "\n" +
|
", offer=" + offer + "\n" +
|
||||||
", contractAsJson=" + contractAsJson + "\n" +
|
", contractAsJson=" + contractAsJson + "\n" +
|
||||||
", contract=" + contract + "\n" +
|
", contract=" + contract + "\n" +
|
||||||
|
|
|
@ -51,6 +51,7 @@ public final class TradeInfoV1Builder {
|
||||||
private String state;
|
private String state;
|
||||||
private String phase;
|
private String phase;
|
||||||
private String periodState;
|
private String periodState;
|
||||||
|
private String payoutState;
|
||||||
private boolean isDepositPublished;
|
private boolean isDepositPublished;
|
||||||
private boolean isDepositUnlocked;
|
private boolean isDepositUnlocked;
|
||||||
private boolean isPaymentSent;
|
private boolean isPaymentSent;
|
||||||
|
@ -130,12 +131,7 @@ public final class TradeInfoV1Builder {
|
||||||
this.volume = volume;
|
this.volume = volume;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeInfoV1Builder withPeriodState(String periodState) {
|
|
||||||
this.periodState = periodState;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TradeInfoV1Builder withState(String state) {
|
public TradeInfoV1Builder withState(String state) {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
return this;
|
return this;
|
||||||
|
@ -146,6 +142,16 @@ public final class TradeInfoV1Builder {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public TradeInfoV1Builder withPeriodState(String periodState) {
|
||||||
|
this.periodState = periodState;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TradeInfoV1Builder withPayoutState(String payoutState) {
|
||||||
|
this.payoutState = payoutState;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public TradeInfoV1Builder withArbitratorNodeAddress(String arbitratorNodeAddress) {
|
public TradeInfoV1Builder withArbitratorNodeAddress(String arbitratorNodeAddress) {
|
||||||
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
this.arbitratorNodeAddress = arbitratorNodeAddress;
|
||||||
return this;
|
return this;
|
||||||
|
|
|
@ -27,6 +27,7 @@ import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.setup.CorePersistedDataHost;
|
import bisq.core.setup.CorePersistedDataHost;
|
||||||
import bisq.core.setup.CoreSetup;
|
import bisq.core.setup.CoreSetup;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
import bisq.core.trade.txproof.xmr.XmrTxProofService;
|
||||||
import bisq.network.p2p.P2PService;
|
import bisq.network.p2p.P2PService;
|
||||||
|
@ -279,6 +280,7 @@ public abstract class HavenoExecutable implements GracefulShutDownHandler, Haven
|
||||||
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
injector.getInstance(TradeStatisticsManager.class).shutDown();
|
||||||
injector.getInstance(XmrTxProofService.class).shutDown();
|
injector.getInstance(XmrTxProofService.class).shutDown();
|
||||||
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
injector.getInstance(AvoidStandbyModeService.class).shutDown();
|
||||||
|
injector.getInstance(TradeManager.class).shutDown();
|
||||||
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService
|
injector.getInstance(XmrWalletService.class).shutDown(); // TODO: why not shut down BtcWalletService, etc? shutdown CoreMoneroConnectionsService
|
||||||
log.info("OpenOfferManager shutdown started");
|
log.info("OpenOfferManager shutdown started");
|
||||||
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
injector.getInstance(OpenOfferManager.class).shutDown(() -> {
|
||||||
|
|
|
@ -556,7 +556,6 @@ public class HavenoSetup {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public static boolean getResyncSpvSemaphore() {
|
public static boolean getResyncSpvSemaphore() {
|
||||||
File resyncSpvSemaphore = new File(Config.appDataDir(), RESYNC_SPV_FILE_NAME);
|
File resyncSpvSemaphore = new File(Config.appDataDir(), RESYNC_SPV_FILE_NAME);
|
||||||
return resyncSpvSemaphore.exists();
|
return resyncSpvSemaphore.exists();
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package bisq.core.btc.wallet;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
|
|
||||||
|
public interface MoneroKeyImageListener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called with changes to the spent status of key images.
|
||||||
|
*/
|
||||||
|
public void onSpentStatusChanged(Map<String, MoneroKeyImageSpentStatus> spentStatuses);
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package bisq.core.btc.wallet;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import monero.common.MoneroError;
|
||||||
|
import monero.common.TaskLooper;
|
||||||
|
import monero.daemon.MoneroDaemon;
|
||||||
|
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Poll for changes to the spent status of key images.
|
||||||
|
*/
|
||||||
|
public class MoneroKeyImagePoller {
|
||||||
|
|
||||||
|
private MoneroDaemon daemon;
|
||||||
|
private long refreshPeriodMs;
|
||||||
|
private List<String> keyImages = new ArrayList<String>();
|
||||||
|
private Set<MoneroKeyImageListener> listeners = new HashSet<MoneroKeyImageListener>();
|
||||||
|
private TaskLooper looper;
|
||||||
|
private Map<String, MoneroKeyImageSpentStatus> lastStatuses = new HashMap<String, MoneroKeyImageSpentStatus>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the listener.
|
||||||
|
*
|
||||||
|
* @param refreshPeriodMs - refresh period in milliseconds
|
||||||
|
* @param keyImages - key images to listen to
|
||||||
|
*/
|
||||||
|
public MoneroKeyImagePoller(MoneroDaemon daemon, long refreshPeriodMs, String... keyImages) {
|
||||||
|
looper = new TaskLooper(() -> poll());
|
||||||
|
setDaemon(daemon);
|
||||||
|
setRefreshPeriodMs(refreshPeriodMs);
|
||||||
|
setKeyImages(keyImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener to receive notifications.
|
||||||
|
*
|
||||||
|
* @param listener - the listener to add
|
||||||
|
*/
|
||||||
|
public void addListener(MoneroKeyImageListener listener) {
|
||||||
|
listeners.add(listener);
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener to receive notifications.
|
||||||
|
*
|
||||||
|
* @param listener - the listener to remove
|
||||||
|
*/
|
||||||
|
public void removeListener(MoneroKeyImageListener listener) {
|
||||||
|
if (!listeners.contains(listener)) throw new MoneroError("Listener is not registered");
|
||||||
|
listeners.remove(listener);
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the Monero daemon to fetch key images from.
|
||||||
|
*
|
||||||
|
* @param daemon - the daemon to fetch key images from
|
||||||
|
*/
|
||||||
|
public void setDaemon(MoneroDaemon daemon) {
|
||||||
|
this.daemon = daemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the Monero daemon to fetch key images from.
|
||||||
|
*
|
||||||
|
* @return the daemon to fetch key images from
|
||||||
|
*/
|
||||||
|
public MoneroDaemon getDaemon() {
|
||||||
|
return daemon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the refresh period in milliseconds.
|
||||||
|
*
|
||||||
|
* @param refreshPeriodMs - the refresh period in milliseconds
|
||||||
|
*/
|
||||||
|
public void setRefreshPeriodMs(long refreshPeriodMs) {
|
||||||
|
this.refreshPeriodMs = refreshPeriodMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the refresh period in milliseconds
|
||||||
|
*
|
||||||
|
* @return the refresh period in milliseconds
|
||||||
|
*/
|
||||||
|
public long getRefreshPeriodMs() {
|
||||||
|
return refreshPeriodMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a copy of the key images being listened to.
|
||||||
|
*
|
||||||
|
* @return the key images to listen to
|
||||||
|
*/
|
||||||
|
public Collection<String> getKeyImages() {
|
||||||
|
return new ArrayList<String>(keyImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the key images to listen to.
|
||||||
|
*
|
||||||
|
* @return the key images to listen to
|
||||||
|
*/
|
||||||
|
public void setKeyImages(String... keyImages) {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
this.keyImages.clear();
|
||||||
|
this.keyImages.addAll(Arrays.asList(keyImages));
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a key image to listen to.
|
||||||
|
*
|
||||||
|
* @param keyImage - the key image to listen to
|
||||||
|
*/
|
||||||
|
public void addKeyImage(String keyImage) {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
addKeyImages(keyImage);
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add key images to listen to.
|
||||||
|
*
|
||||||
|
* @param keyImages - key images to listen to
|
||||||
|
*/
|
||||||
|
public void addKeyImages(String... keyImages) {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
for (String keyImage : keyImages) if (!this.keyImages.contains(keyImage)) this.keyImages.add(keyImage);
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a key image to listen to.
|
||||||
|
*
|
||||||
|
* @param keyImage - the key image to unlisten to
|
||||||
|
*/
|
||||||
|
public void removeKeyImage(String keyImage) {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
removeKeyImages(keyImage);
|
||||||
|
refreshPolling();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove key images to listen to.
|
||||||
|
*
|
||||||
|
* @param keyImages - key images to unlisten to
|
||||||
|
*/
|
||||||
|
public void removeKeyImages(String... keyImages) {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
for (String keyImage : keyImages) if (!this.keyImages.contains(keyImage)) throw new MoneroError("Key image not registered with poller: " + keyImage);
|
||||||
|
this.keyImages.removeAll(Arrays.asList(keyImages));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void poll() {
|
||||||
|
synchronized (keyImages) {
|
||||||
|
|
||||||
|
// fetch spent statuses
|
||||||
|
List<MoneroKeyImageSpentStatus> spentStatuses = keyImages.isEmpty() ? new ArrayList<MoneroKeyImageSpentStatus>() : daemon.getKeyImageSpentStatuses(keyImages);
|
||||||
|
|
||||||
|
// collect changed statuses
|
||||||
|
Map<String, MoneroKeyImageSpentStatus> changedStatuses = new HashMap<String, MoneroKeyImageSpentStatus>();
|
||||||
|
for (int i = 0; i < keyImages.size(); i++) {
|
||||||
|
if (lastStatuses.get(keyImages.get(i)) != spentStatuses.get(i)) {
|
||||||
|
lastStatuses.put(keyImages.get(i), spentStatuses.get(i));
|
||||||
|
changedStatuses.put(keyImages.get(i), spentStatuses.get(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// announce changes
|
||||||
|
for (MoneroKeyImageListener listener : new ArrayList<MoneroKeyImageListener>(listeners)) listener.onSpentStatusChanged(changedStatuses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refreshPolling() {
|
||||||
|
setIsPolling(listeners.size() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setIsPolling(boolean isPolling) {
|
||||||
|
if (isPolling) looper.start(refreshPeriodMs);
|
||||||
|
else looper.stop();
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,8 +20,10 @@ import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.SellerTrade;
|
import bisq.core.trade.SellerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.util.ParsingUtils;
|
import bisq.core.util.ParsingUtils;
|
||||||
|
|
||||||
|
import com.google.common.collect.TreeMultimap;
|
||||||
import com.google.common.util.concurrent.Service.State;
|
import com.google.common.util.concurrent.Service.State;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
import common.utils.JsonUtils;
|
import common.utils.JsonUtils;
|
||||||
|
@ -29,6 +31,7 @@ import java.io.File;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -47,6 +50,7 @@ import monero.common.MoneroRpcConnection;
|
||||||
import monero.common.MoneroRpcError;
|
import monero.common.MoneroRpcError;
|
||||||
import monero.common.MoneroUtils;
|
import monero.common.MoneroUtils;
|
||||||
import monero.daemon.MoneroDaemonRpc;
|
import monero.daemon.MoneroDaemonRpc;
|
||||||
|
import monero.daemon.model.MoneroKeyImageSpentStatus;
|
||||||
import monero.daemon.model.MoneroNetworkType;
|
import monero.daemon.model.MoneroNetworkType;
|
||||||
import monero.daemon.model.MoneroOutput;
|
import monero.daemon.model.MoneroOutput;
|
||||||
import monero.daemon.model.MoneroSubmitTxResult;
|
import monero.daemon.model.MoneroSubmitTxResult;
|
||||||
|
@ -97,6 +101,7 @@ public class XmrWalletService {
|
||||||
private TradeManager tradeManager;
|
private TradeManager tradeManager;
|
||||||
private MoneroWalletRpc wallet;
|
private MoneroWalletRpc wallet;
|
||||||
private Map<String, MoneroWallet> multisigWallets;
|
private Map<String, MoneroWallet> multisigWallets;
|
||||||
|
private Map<String, Object> walletLocks = new HashMap<String, Object>();
|
||||||
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
private final Map<String, Optional<MoneroTx>> txCache = new HashMap<String, Optional<MoneroTx>>();
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
|
@ -182,29 +187,40 @@ public class XmrWalletService {
|
||||||
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
|
return accountService.getPassword() == null ? MONERO_WALLET_RPC_DEFAULT_PASSWORD : accountService.getPassword();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean multisigWalletExists(String tradeId) {
|
private synchronized void initWalletLock(String id) {
|
||||||
return walletExists(MONERO_MULTISIG_WALLET_PREFIX + tradeId);
|
if (!walletLocks.containsKey(id)) walletLocks.put(id, new Object());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean multisigWalletExists(String tradeId) {
|
||||||
|
initWalletLock(tradeId);
|
||||||
|
synchronized(walletLocks.get(tradeId)) {
|
||||||
|
return walletExists(MONERO_MULTISIG_WALLET_PREFIX + tradeId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): test retaking failed trade. create new multisig wallet or replace? cannot reuse
|
|
||||||
public MoneroWallet createMultisigWallet(String tradeId) {
|
public MoneroWallet createMultisigWallet(String tradeId) {
|
||||||
log.info("{}.createMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
initWalletLock(tradeId);
|
||||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
synchronized(walletLocks.get(tradeId)) {
|
||||||
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
log.info("{}.createMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
||||||
MoneroWallet multisigWallet = createWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null, false); // auto-assign port
|
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||||
multisigWallets.put(tradeId, multisigWallet);
|
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
||||||
return multisigWallet;
|
MoneroWallet multisigWallet = createWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null, true); // auto-assign port
|
||||||
|
multisigWallets.put(tradeId, multisigWallet);
|
||||||
|
return multisigWallet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): provide progress notifications during open?
|
// TODO (woodser): provide progress notifications during open?
|
||||||
public MoneroWallet getMultisigWallet(String tradeId) {
|
public MoneroWallet getMultisigWallet(String tradeId) {
|
||||||
log.info("{}.getMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
initWalletLock(tradeId);
|
||||||
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
synchronized(walletLocks.get(tradeId)) {
|
||||||
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
if (multisigWallets.containsKey(tradeId)) return multisigWallets.get(tradeId);
|
||||||
if (!walletExists(path)) throw new RuntimeException("Multisig wallet does not exist for trade " + tradeId);
|
String path = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
||||||
MoneroWallet multisigWallet = openWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null);
|
if (!walletExists(path)) throw new RuntimeException("Multisig wallet does not exist for trade " + tradeId);
|
||||||
multisigWallets.put(tradeId, multisigWallet);
|
MoneroWallet multisigWallet = openWallet(new MoneroWalletConfig().setPath(path).setPassword(getWalletPassword()), null);
|
||||||
return multisigWallet;
|
multisigWallets.put(tradeId, multisigWallet);
|
||||||
|
return multisigWallet;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveWallet(MoneroWallet wallet) {
|
public void saveWallet(MoneroWallet wallet) {
|
||||||
|
@ -213,19 +229,25 @@ public class XmrWalletService {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void closeMultisigWallet(String tradeId) {
|
public void closeMultisigWallet(String tradeId) {
|
||||||
log.info("{}.closeMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
initWalletLock(tradeId);
|
||||||
if (!multisigWallets.containsKey(tradeId)) throw new RuntimeException("Multisig wallet to close was not previously opened for trade " + tradeId);
|
synchronized(walletLocks.get(tradeId)) {
|
||||||
MoneroWallet wallet = multisigWallets.remove(tradeId);
|
log.info("{}.closeMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
||||||
closeWallet(wallet, true);
|
if (!multisigWallets.containsKey(tradeId)) throw new RuntimeException("Multisig wallet to close was not previously opened for trade " + tradeId);
|
||||||
|
MoneroWallet wallet = multisigWallets.remove(tradeId);
|
||||||
|
closeWallet(wallet, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteMultisigWallet(String tradeId) {
|
public boolean deleteMultisigWallet(String tradeId) {
|
||||||
log.info("{}.deleteMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
initWalletLock(tradeId);
|
||||||
String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
synchronized(walletLocks.get(tradeId)) {
|
||||||
if (!walletExists(walletName)) return false;
|
log.info("{}.deleteMultisigWallet({})", getClass().getSimpleName(), tradeId);
|
||||||
if (multisigWallets.containsKey(tradeId)) closeMultisigWallet(tradeId); // TODO: synchronize
|
String walletName = MONERO_MULTISIG_WALLET_PREFIX + tradeId;
|
||||||
deleteWallet(walletName);
|
if (!walletExists(walletName)) return false;
|
||||||
return true;
|
if (multisigWallets.containsKey(tradeId)) closeMultisigWallet(tradeId);
|
||||||
|
deleteWallet(walletName);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
public MoneroTxWallet createTx(List<MoneroDestination> destinations) {
|
||||||
|
@ -254,21 +276,20 @@ public class XmrWalletService {
|
||||||
// get expected mining fee
|
// get expected mining fee
|
||||||
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet miningFeeTx = wallet.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||||
.addDestination(returnAddress, depositAmount));
|
.addDestination(returnAddress, depositAmount));
|
||||||
BigInteger miningFee = miningFeeTx.getFee();
|
BigInteger miningFee = miningFeeTx.getFee();
|
||||||
|
|
||||||
// create reserve tx
|
// create reserve tx
|
||||||
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet reserveTx = wallet.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||||
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
.addDestination(returnAddress, depositAmount.add(miningFee.multiply(BigInteger.valueOf(3l))))); // add thrice the mining fee // TODO (woodser): really require more funds on top of security deposit?
|
||||||
|
|
||||||
// freeze inputs
|
// freeze inputs
|
||||||
if (freezeInputs) {
|
if (freezeInputs) {
|
||||||
for (MoneroOutput input : reserveTx.getInputs()) {
|
for (MoneroOutput input : reserveTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
wallet.save();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return reserveTx;
|
return reserveTx;
|
||||||
|
@ -291,13 +312,12 @@ public class XmrWalletService {
|
||||||
// create deposit tx
|
// create deposit tx
|
||||||
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
MoneroTxWallet depositTx = wallet.createTx(new MoneroTxConfig()
|
||||||
.setAccountIndex(0)
|
.setAccountIndex(0)
|
||||||
.addDestination(TradeUtils.getTradeFeeAddress(), tradeFee)
|
.addDestination(HavenoUtils.getTradeFeeAddress(), tradeFee)
|
||||||
.addDestination(multisigAddress, depositAmount));
|
.addDestination(multisigAddress, depositAmount));
|
||||||
|
|
||||||
// freeze deposit inputs
|
// freeze deposit inputs
|
||||||
for (MoneroOutput input : depositTx.getInputs()) {
|
for (MoneroOutput input : depositTx.getInputs()) wallet.freezeOutput(input.getKeyImage().getHex());
|
||||||
wallet.freezeOutput(input.getKeyImage().getHex());
|
wallet.save();
|
||||||
}
|
|
||||||
|
|
||||||
return depositTx;
|
return depositTx;
|
||||||
}
|
}
|
||||||
|
@ -342,7 +362,7 @@ public class XmrWalletService {
|
||||||
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
if (tx.getUnlockHeight() != 0) throw new RuntimeException("Unlock height must be 0");
|
||||||
|
|
||||||
// verify trade fee
|
// verify trade fee
|
||||||
String feeAddress = TradeUtils.getTradeFeeAddress();
|
String feeAddress = HavenoUtils.getTradeFeeAddress();
|
||||||
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
MoneroCheckTx check = wallet.checkTxKey(txHash, txKey, feeAddress);
|
||||||
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
if (!check.isGood()) throw new RuntimeException("Invalid proof of trade fee");
|
||||||
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
if (!check.getReceivedAmount().equals(tradeFee)) throw new RuntimeException("Trade fee is incorrect amount, expected " + tradeFee + " but was " + check.getReceivedAmount());
|
||||||
|
@ -435,10 +455,8 @@ public class XmrWalletService {
|
||||||
// initialize main wallet if connected or previously created
|
// initialize main wallet if connected or previously created
|
||||||
maybeInitMainWallet();
|
maybeInitMainWallet();
|
||||||
|
|
||||||
// update wallet connections on change
|
// set and listen to daemon connection
|
||||||
connectionsService.addListener(newConnection -> {
|
connectionsService.addListener(newConnection -> setDaemonConnection(newConnection));
|
||||||
setWalletDaemonConnections(newConnection);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean walletExists(String walletName) {
|
private boolean walletExists(String walletName) {
|
||||||
|
@ -580,17 +598,13 @@ public class XmrWalletService {
|
||||||
return MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
|
return MONERO_WALLET_RPC_MANAGER.startInstance(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setWalletDaemonConnections(MoneroRpcConnection connection) {
|
private void setDaemonConnection(MoneroRpcConnection connection) {
|
||||||
log.info("Setting wallet daemon connection: " + (connection == null ? null : connection.getUri()));
|
log.info("Setting wallet daemon connection: " + (connection == null ? null : connection.getUri()));
|
||||||
if (wallet == null) maybeInitMainWallet();
|
if (wallet == null) maybeInitMainWallet();
|
||||||
if (wallet != null) {
|
if (wallet != null) {
|
||||||
wallet.setDaemonConnection(connection);
|
wallet.setDaemonConnection(connection);
|
||||||
wallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
|
wallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs());
|
||||||
}
|
}
|
||||||
for (MoneroWallet multisigWallet : multisigWallets.values()) {
|
|
||||||
multisigWallet.setDaemonConnection(connection);
|
|
||||||
multisigWallet.startSyncing(connectionsService.getDefaultRefreshPeriodMs()); // TODO: optimize when multisig wallets are open and syncing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyBalanceListeners() {
|
private void notifyBalanceListeners() {
|
||||||
|
@ -663,7 +677,7 @@ public class XmrWalletService {
|
||||||
if (!new File(path).delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
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 + ".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);
|
if (!new File(path + ".address.txt").delete()) throw new RuntimeException("Failed to delete wallet file: " + path);
|
||||||
deleteBackupWallets(walletName);
|
deleteBackupWallets(walletName); // TODO: retain backup for some time?
|
||||||
}
|
}
|
||||||
|
|
||||||
private void closeAllWallets() {
|
private void closeAllWallets() {
|
||||||
|
@ -675,30 +689,16 @@ public class XmrWalletService {
|
||||||
openWallets.add(multisigWallets.get(multisigWalletKey));
|
openWallets.add(multisigWallets.get(multisigWalletKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
// done if no open wallets
|
// close wallets in parallel
|
||||||
if (openWallets.isEmpty()) return;
|
Set<Runnable> tasks = new HashSet<Runnable>();
|
||||||
|
for (MoneroWallet wallet : openWallets) tasks.add(() -> {
|
||||||
// close all wallets in parallel
|
try {
|
||||||
ExecutorService pool = Executors.newFixedThreadPool(Math.min(10, openWallets.size()));
|
closeWallet(wallet, true);
|
||||||
for (MoneroWallet openWallet : openWallets) {
|
} catch (Exception e) {
|
||||||
pool.submit(new Runnable() {
|
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
|
||||||
@Override
|
}
|
||||||
public void run() {
|
});
|
||||||
try {
|
HavenoUtils.awaitTasks(tasks);
|
||||||
closeWallet(openWallet, true);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("Error closing monero-wallet-rpc subprocess. Was Haveno stopped manually with ctrl+c?");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
pool.shutdown();
|
|
||||||
try {
|
|
||||||
if (!pool.awaitTermination(60000, TimeUnit.SECONDS)) pool.shutdownNow();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
pool.shutdownNow();
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear wallets
|
// clear wallets
|
||||||
wallet = null;
|
wallet = null;
|
||||||
|
|
|
@ -82,13 +82,11 @@ public class TradeEvents {
|
||||||
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
msg = Res.get("account.notifications.trade.message.msg.started", shortId);
|
||||||
break;
|
break;
|
||||||
case PAYMENT_RECEIVED:
|
case PAYMENT_RECEIVED:
|
||||||
break;
|
|
||||||
case PAYOUT_PUBLISHED:
|
|
||||||
// We only notify the buyer
|
// We only notify the buyer
|
||||||
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
if (trade.getContract() != null && pubKeyRingProvider.get().equals(trade.getContract().getBuyerPubKeyRing()))
|
||||||
msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
|
msg = Res.get("account.notifications.trade.message.msg.completed", shortId);
|
||||||
break;
|
break;
|
||||||
case WITHDRAWN:
|
case COMPLETED:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (msg != null) {
|
if (msg != null) {
|
||||||
|
|
|
@ -23,12 +23,10 @@ import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.locale.CurrencyUtil;
|
import bisq.core.locale.CurrencyUtil;
|
||||||
import bisq.core.locale.Res;
|
import bisq.core.locale.Res;
|
||||||
import bisq.core.monetary.Price;
|
import bisq.core.monetary.Price;
|
||||||
import bisq.core.offer.availability.DisputeAgentSelection;
|
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountUtil;
|
import bisq.core.payment.PaymentAccountUtil;
|
||||||
import bisq.core.provider.price.MarketPrice;
|
import bisq.core.provider.price.MarketPrice;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
@ -48,9 +46,6 @@ import org.bitcoinj.core.Coin;
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import bisq.core.filter.FilterManager;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
import bisq.core.payment.PaymentAccountUtil;
|
import bisq.core.payment.PaymentAccountUtil;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
|
||||||
|
@ -224,6 +224,6 @@ public class OfferFilterService {
|
||||||
public boolean hasValidSignature(Offer offer) {
|
public boolean hasValidSignature(Offer offer) {
|
||||||
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
Arbitrator arbitrator = user.getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner());
|
||||||
if (arbitrator == null) return false; // invalid arbitrator
|
if (arbitrator == null) return false; // invalid arbitrator
|
||||||
return TradeUtils.isArbitratorSignatureValid(offer, arbitrator);
|
return HavenoUtils.isArbitratorSignatureValid(offer, arbitrator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ import bisq.core.support.dispute.arbitration.arbitrator.ArbitratorManager;
|
||||||
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
import bisq.core.support.dispute.mediation.mediator.MediatorManager;
|
||||||
import bisq.core.trade.ClosedTradableManager;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.TradableList;
|
import bisq.core.trade.TradableList;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.handlers.TransactionResultHandler;
|
import bisq.core.trade.handlers.TransactionResultHandler;
|
||||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
|
@ -564,6 +564,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
Offer offer = openOffer.getOffer();
|
Offer offer = openOffer.getOffer();
|
||||||
if (offer.getOfferPayload().getReserveTxKeyImages() != null) {
|
if (offer.getOfferPayload().getReserveTxKeyImages() != null) {
|
||||||
for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage);
|
for (String frozenKeyImage : offer.getOfferPayload().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(frozenKeyImage);
|
||||||
|
xmrWalletService.getWallet().save();
|
||||||
}
|
}
|
||||||
offer.setState(Offer.State.REMOVED);
|
offer.setState(Offer.State.REMOVED);
|
||||||
openOffer.setState(OpenOffer.State.CANCELED);
|
openOffer.setState(OpenOffer.State.CANCELED);
|
||||||
|
@ -634,7 +635,7 @@ public class OpenOfferManager implements PeerManager.Listener, DecryptedDirectMe
|
||||||
latch.countDown();
|
latch.countDown();
|
||||||
errorMessages.add(errorMessage);
|
errorMessages.add(errorMessage);
|
||||||
});
|
});
|
||||||
TradeUtils.awaitLatch(latch);
|
HavenoUtils.awaitLatch(latch);
|
||||||
}
|
}
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
if (errorMessages.size() > 0) errorMessageHandler.handleErrorMessage(errorMessages.toString());
|
if (errorMessages.size() > 0) errorMessageHandler.handleErrorMessage(errorMessages.toString());
|
||||||
|
|
|
@ -21,7 +21,7 @@ import bisq.core.offer.AvailabilityResult;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.availability.OfferAvailabilityModel;
|
import bisq.core.offer.availability.OfferAvailabilityModel;
|
||||||
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
import bisq.core.offer.messages.OfferAvailabilityResponse;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.common.taskrunner.Task;
|
import bisq.common.taskrunner.Task;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ public class ProcessOfferAvailabilityResponse extends Task<OfferAvailabilityMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// verify maker signature for trade request
|
// verify maker signature for trade request
|
||||||
if (!TradeUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
|
if (!HavenoUtils.isMakerSignatureValid(model.getTradeRequest(), offerAvailabilityResponse.getMakerSignature(), offer.getPubKeyRing())) {
|
||||||
offer.setState(Offer.State.NOT_AVAILABLE);
|
offer.setState(Offer.State.NOT_AVAILABLE);
|
||||||
failed("Take offer attempt failed because maker signature is invalid");
|
failed("Take offer attempt failed because maker signature is invalid");
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -62,7 +62,7 @@ public class PlaceOfferProtocol {
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
public void placeOffer() {
|
public void placeOffer() {
|
||||||
log.debug("placeOffer() " + model.getOffer().getId());
|
log.info("{}.placeOffer() {}", getClass().getSimpleName(), model.getOffer().getId());
|
||||||
|
|
||||||
timeoutTimer = UserThread.runAfter(() -> {
|
timeoutTimer = UserThread.runAfter(() -> {
|
||||||
handleError(Res.get("createOffer.timeoutAtPublishing"));
|
handleError(Res.get("createOffer.timeoutAtPublishing"));
|
||||||
|
|
|
@ -20,7 +20,7 @@ package bisq.core.offer.placeoffer.tasks;
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
import bisq.core.offer.placeoffer.PlaceOfferModel;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ public class MakerProcessSignOfferResponse extends Task<PlaceOfferModel> {
|
||||||
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null");
|
Arbitrator arbitrator = checkNotNull(model.getUser().getAcceptedArbitratorByAddress(offer.getOfferPayload().getArbitratorSigner()), "user.getAcceptedArbitratorByAddress(arbitratorSigner) must not be null");
|
||||||
|
|
||||||
// validate arbitrator signature
|
// validate arbitrator signature
|
||||||
if (!TradeUtils.isArbitratorSignatureValid(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), arbitrator)) {
|
if (!HavenoUtils.isArbitratorSignatureValid(new Offer(model.getSignOfferResponse().getSignedOfferPayload()), arbitrator)) {
|
||||||
throw new RuntimeException("Offer payload has invalid arbitrator signature");
|
throw new RuntimeException("Offer payload has invalid arbitrator signature");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,22 +39,18 @@ import bisq.core.support.dispute.messages.PeerOpenedDisputeMessage;
|
||||||
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
import bisq.core.support.dispute.refund.refundagent.RefundAgent;
|
||||||
import bisq.core.support.messages.ChatMessage;
|
import bisq.core.support.messages.ChatMessage;
|
||||||
import bisq.core.trade.messages.PaymentSentMessage;
|
import bisq.core.trade.messages.PaymentSentMessage;
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.messages.DepositRequest;
|
import bisq.core.trade.messages.DepositRequest;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
import bisq.core.trade.messages.MediatedPayoutTxPublishedMessage;
|
||||||
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
import bisq.core.trade.messages.MediatedPayoutTxSignatureMessage;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyRequest;
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
import bisq.core.trade.messages.RefreshTradeStateRequest;
|
||||||
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.messages.TraderSignedWitnessMessage;
|
import bisq.core.trade.messages.TraderSignedWitnessMessage;
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
|
||||||
import bisq.core.trade.messages.UpdateMultisigResponse;
|
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessage;
|
import bisq.network.p2p.AckMessage;
|
||||||
import bisq.network.p2p.BundleOfEnvelopes;
|
import bisq.network.p2p.BundleOfEnvelopes;
|
||||||
|
@ -158,21 +154,13 @@ public class CoreNetworkProtoResolver extends CoreProtoResolver implements Netwo
|
||||||
return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion);
|
return DepositRequest.fromProto(proto.getDepositRequest(), this, messageVersion);
|
||||||
case DEPOSIT_RESPONSE:
|
case DEPOSIT_RESPONSE:
|
||||||
return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion);
|
return DepositResponse.fromProto(proto.getDepositResponse(), this, messageVersion);
|
||||||
case PAYMENT_ACCOUNT_KEY_REQUEST:
|
case DEPOSITS_CONFIRMED_MESSAGE:
|
||||||
return PaymentAccountKeyRequest.fromProto(proto.getPaymentAccountKeyRequest(), this, messageVersion);
|
return DepositsConfirmedMessage.fromProto(proto.getDepositsConfirmedMessage(), this, messageVersion);
|
||||||
case PAYMENT_ACCOUNT_KEY_RESPONSE:
|
|
||||||
return PaymentAccountKeyResponse.fromProto(proto.getPaymentAccountKeyResponse(), this, messageVersion);
|
|
||||||
case UPDATE_MULTISIG_REQUEST:
|
|
||||||
return UpdateMultisigRequest.fromProto(proto.getUpdateMultisigRequest(), this, messageVersion);
|
|
||||||
case UPDATE_MULTISIG_RESPONSE:
|
|
||||||
return UpdateMultisigResponse.fromProto(proto.getUpdateMultisigResponse(), this, messageVersion);
|
|
||||||
|
|
||||||
case PAYMENT_SENT_MESSAGE:
|
case PAYMENT_SENT_MESSAGE:
|
||||||
return PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion);
|
return PaymentSentMessage.fromProto(proto.getPaymentSentMessage(), messageVersion);
|
||||||
case PAYMENT_RECEIVED_MESSAGE:
|
case PAYMENT_RECEIVED_MESSAGE:
|
||||||
return PaymentReceivedMessage.fromProto(proto.getPaymentReceivedMessage(), messageVersion);
|
return PaymentReceivedMessage.fromProto(proto.getPaymentReceivedMessage(), messageVersion);
|
||||||
case PAYOUT_TX_PUBLISHED_MESSAGE:
|
|
||||||
return PayoutTxPublishedMessage.fromProto(proto.getPayoutTxPublishedMessage(), messageVersion);
|
|
||||||
|
|
||||||
case TRADER_SIGNED_WITNESS_MESSAGE:
|
case TRADER_SIGNED_WITNESS_MESSAGE:
|
||||||
return TraderSignedWitnessMessage.fromProto(proto.getTraderSignedWitnessMessage(), messageVersion);
|
return TraderSignedWitnessMessage.fromProto(proto.getTraderSignedWitnessMessage(), messageVersion);
|
||||||
|
|
|
@ -333,13 +333,10 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
if (isAgent(dispute)) {
|
if (isAgent(dispute)) {
|
||||||
|
|
||||||
// update arbitrator's multisig wallet
|
// update arbitrator's multisig wallet
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
trade.syncWallet();
|
||||||
multisigWallet.importMultisigHex(openNewDisputeMessage.getUpdatedMultisigHex());
|
trade.getWallet().importMultisigHex(openNewDisputeMessage.getUpdatedMultisigHex());
|
||||||
|
trade.saveWallet();
|
||||||
log.info("Arbitrator multisig wallet updated on new dispute message for trade " + dispute.getTradeId());
|
log.info("Arbitrator multisig wallet updated on new dispute message for trade " + dispute.getTradeId());
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
xmrWalletService.closeMultisigWallet(dispute.getTradeId());
|
|
||||||
|
|
||||||
synchronized (disputeList) {
|
synchronized (disputeList) {
|
||||||
if (!disputeList.contains(dispute)) {
|
if (!disputeList.contains(dispute)) {
|
||||||
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
Optional<Dispute> storedDisputeOptional = findDispute(dispute);
|
||||||
|
@ -748,6 +745,15 @@ public abstract class DisputeManager<T extends DisputeList<Dispute>> extends Sup
|
||||||
disputeResultMessage.getTradeId(), disputeResultMessage.getUid(),
|
disputeResultMessage.getTradeId(), disputeResultMessage.getUid(),
|
||||||
chatMessage.getUid());
|
chatMessage.getUid());
|
||||||
|
|
||||||
|
// TODO: hack to sync wallet after dispute message received in order to detect payout published
|
||||||
|
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
||||||
|
long defaultRefreshPeriod = xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
UserThread.runAfter(() -> {
|
||||||
|
if (!trade.isPayoutUnlocked()) trade.syncWallet();
|
||||||
|
}, defaultRefreshPeriod / 1000 * (i + 1));
|
||||||
|
}
|
||||||
|
|
||||||
// We use the chatMessage wrapped inside the disputeResultMessage for
|
// We use the chatMessage wrapped inside the disputeResultMessage for
|
||||||
// the state, as that is displayed to the user and we only persist that msg
|
// the state, as that is displayed to the user and we only persist that msg
|
||||||
chatMessage.setArrived(true);
|
chatMessage.setArrived(true);
|
||||||
|
|
|
@ -301,7 +301,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
log.trace("We don't publish the tx as we are not the winning party.");
|
log.trace("We don't publish the tx as we are not the winning party.");
|
||||||
// Clean up tangling trades
|
// Clean up tangling trades
|
||||||
if (dispute.disputeResultProperty().get() != null && dispute.isClosed()) {
|
if (dispute.disputeResultProperty().get() != null && dispute.isClosed()) {
|
||||||
updateTradeOrOpenOfferManager(tradeId);
|
closeTradeOrOffer(tradeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +324,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
// We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
|
// We prefer to close the dispute in that case. If there was no deposit tx and a random tx was used
|
||||||
// we get a TransactionVerificationException. No reason to keep that dispute open...
|
// we get a TransactionVerificationException. No reason to keep that dispute open...
|
||||||
updateTradeOrOpenOfferManager(tradeId); // TODO (woodser): only close in case of verification exception?
|
closeTradeOrOffer(tradeId); // TODO (woodser): only close in case of verification exception?
|
||||||
|
|
||||||
throw new RuntimeException(errorMessage);
|
throw new RuntimeException(errorMessage);
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -338,7 +338,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId); // TODO (woodser): this is closed after sending ArbitratorPayoutTxRequest to arbitrator which opens and syncs multisig and responds with signed dispute tx. more efficient way is to include with arbitrator-signed dispute tx with dispute result?
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(tradeId); // TODO (woodser): this is closed after sending ArbitratorPayoutTxRequest to arbitrator which opens and syncs multisig and responds with signed dispute tx. more efficient way is to include with arbitrator-signed dispute tx with dispute result?
|
||||||
sendArbitratorPayoutTxRequest(multisigWallet.exportMultisigHex(), dispute, contract);
|
sendArbitratorPayoutTxRequest(multisigWallet.exportMultisigHex(), dispute, contract);
|
||||||
xmrWalletService.closeMultisigWallet(tradeId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,12 +375,13 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
cleanupRetryMap(uid);
|
cleanupRetryMap(uid);
|
||||||
|
|
||||||
// update multisig wallet
|
// update trade wallet
|
||||||
if (xmrWalletService.multisigWalletExists(tradeId)) { // TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
|
MoneroWallet wallet = trade.getWallet();
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
if (wallet != null) { // TODO: multisig wallet may already be deleted if peer completed trade with arbitrator. refactor trade completion?
|
||||||
multisigWallet.importMultisigHex(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex());
|
trade.syncWallet();
|
||||||
MoneroTxWallet parsedPayoutTx = multisigWallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
wallet.importMultisigHex(peerPublishedDisputePayoutTxMessage.getUpdatedMultisigHex());
|
||||||
xmrWalletService.closeMultisigWallet(tradeId);
|
trade.saveWallet();
|
||||||
|
MoneroTxWallet parsedPayoutTx = wallet.describeTxSet(new MoneroTxSet().setMultisigTxHex(peerPublishedDisputePayoutTxMessage.getPayoutTxHex())).getTxs().get(0);
|
||||||
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
dispute.setDisputePayoutTxId(parsedPayoutTx.getHash());
|
||||||
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
XmrWalletService.printTxs("Disputed payoutTx received from peer", parsedPayoutTx);
|
||||||
}
|
}
|
||||||
|
@ -397,6 +397,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Arbitrator receives updated multisig hex from dispute opener's peer (if co-signer) and returns updated payout tx to be signed and published
|
// Arbitrator receives updated multisig hex from dispute opener's peer (if co-signer) and returns updated payout tx to be signed and published
|
||||||
|
// TODO: this should be invoked from mailbox message and send mailbox message response to support offline arbitrator
|
||||||
private void onArbitratorPayoutTxRequest(ArbitratorPayoutTxRequest request) {
|
private void onArbitratorPayoutTxRequest(ArbitratorPayoutTxRequest request) {
|
||||||
log.info("{}.onArbitratorPayoutTxRequest()", getClass().getSimpleName());
|
log.info("{}.onArbitratorPayoutTxRequest()", getClass().getSimpleName());
|
||||||
String tradeId = request.getTradeId();
|
String tradeId = request.getTradeId();
|
||||||
|
@ -426,9 +427,11 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
}
|
}
|
||||||
|
|
||||||
// update arbitrator's multisig wallet with co-signer's multisig hex
|
// update arbitrator's multisig wallet with co-signer's multisig hex
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(dispute.getTradeId());
|
trade.syncWallet();
|
||||||
|
MoneroWallet multisigWallet = trade.getWallet();
|
||||||
try {
|
try {
|
||||||
multisigWallet.importMultisigHex(request.getUpdatedMultisigHex());
|
multisigWallet.importMultisigHex(request.getUpdatedMultisigHex());
|
||||||
|
trade.saveWallet();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
|
log.warn("Failed to import multisig hex from payout co-signer for trade id " + tradeId);
|
||||||
return;
|
return;
|
||||||
|
@ -439,9 +442,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
System.out.println("Arbitrator created updated payout tx for co-signer!!!");
|
System.out.println("Arbitrator created updated payout tx for co-signer!!!");
|
||||||
System.out.println(payoutTx);
|
System.out.println(payoutTx);
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
xmrWalletService.closeMultisigWallet(tradeId);
|
|
||||||
|
|
||||||
// send updated payout tx to sender
|
// send updated payout tx to sender
|
||||||
PubKeyRing senderPubKeyRing = contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress()) ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
PubKeyRing senderPubKeyRing = contract.getBuyerNodeAddress().equals(request.getSenderNodeAddress()) ? contract.getBuyerPubKeyRing() : contract.getSellerPubKeyRing();
|
||||||
ArbitratorPayoutTxResponse response = new ArbitratorPayoutTxResponse(
|
ArbitratorPayoutTxResponse response = new ArbitratorPayoutTxResponse(
|
||||||
|
@ -455,10 +455,19 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
senderPubKeyRing,
|
senderPubKeyRing,
|
||||||
response,
|
response,
|
||||||
new SendDirectMessageListener() {
|
new SendDirectMessageListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onArrived() {
|
public void onArrived() {
|
||||||
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
log.info("{} arrived at peer {}. tradeId={}, uid={}",
|
||||||
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
|
response.getClass().getSimpleName(), request.getSenderNodeAddress(), dispute.getTradeId(), response.getUid());
|
||||||
|
|
||||||
|
// TODO: hack to sync wallet after dispute message received in order to detect payout published
|
||||||
|
Trade trade = tradeManager.getTrade(dispute.getTradeId());
|
||||||
|
long defaultRefreshPeriod = xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs();
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
UserThread.runAfter(() -> {
|
||||||
|
if (!trade.isPayoutUnlocked()) trade.syncWallet();
|
||||||
|
}, defaultRefreshPeriod / 1000 * (i + 1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -546,6 +555,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
|
|
||||||
// update multisig wallet from arbitrator
|
// update multisig wallet from arbitrator
|
||||||
multisigWallet.importMultisigHex(disputeResult.getArbitratorUpdatedMultisigHex());
|
multisigWallet.importMultisigHex(disputeResult.getArbitratorUpdatedMultisigHex());
|
||||||
|
xmrWalletService.saveWallet(multisigWallet);
|
||||||
|
|
||||||
// sign arbitrator-signed payout tx
|
// sign arbitrator-signed payout tx
|
||||||
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
|
MoneroMultisigSignResult result = multisigWallet.signMultisigTxHex(payoutTxHex);
|
||||||
|
@ -575,10 +585,10 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
// update state
|
// update state
|
||||||
trade.setPayoutTx(txSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
|
trade.setPayoutTx(txSet.getTxs().get(0)); // TODO (woodser): is trade.payoutTx() mutually exclusive from dispute payout tx?
|
||||||
trade.setPayoutTxId(txSet.getTxs().get(0).getHash());
|
trade.setPayoutTxId(txSet.getTxs().get(0).getHash());
|
||||||
trade.setState(Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
trade.setPayoutState(Trade.PayoutState.PUBLISHED);
|
||||||
dispute.setDisputePayoutTxId(txSet.getTxs().get(0).getHash());
|
dispute.setDisputePayoutTxId(txSet.getTxs().get(0).getHash());
|
||||||
sendPeerPublishedPayoutTxMessage(multisigWallet.exportMultisigHex(), txSet.getMultisigTxHex(), dispute, contract);
|
sendPeerPublishedPayoutTxMessage(multisigWallet.exportMultisigHex(), txSet.getMultisigTxHex(), dispute, contract);
|
||||||
updateTradeOrOpenOfferManager(tradeId);
|
closeTradeOrOffer(tradeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -623,7 +633,7 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateTradeOrOpenOfferManager(String tradeId) {
|
public void closeTradeOrOffer(String tradeId) {
|
||||||
// set state after payout as we call swapTradeEntryToAvailableEntry
|
// set state after payout as we call swapTradeEntryToAvailableEntry
|
||||||
if (tradeManager.getOpenTrade(tradeId).isPresent()) {
|
if (tradeManager.getOpenTrade(tradeId).isPresent()) {
|
||||||
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.DISPUTE_CLOSED);
|
tradeManager.closeDisputedTrade(tradeId, Trade.DisputeState.DISPUTE_CLOSED);
|
||||||
|
@ -632,7 +642,6 @@ public final class ArbitrationManager extends DisputeManager<ArbitrationDisputeL
|
||||||
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
openOfferOptional.ifPresent(openOffer -> openOfferManager.closeOpenOffer(openOffer.getOffer()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx
|
// dispute opener's peer signs payout tx by sending updated multisig hex to arbitrator who returns updated payout tx
|
||||||
private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) {
|
private void sendArbitratorPayoutTxRequest(String updatedMultisigHex, Dispute dispute, Contract contract) {
|
||||||
ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest(
|
ArbitratorPayoutTxRequest request = new ArbitratorPayoutTxRequest(
|
||||||
|
|
|
@ -64,7 +64,7 @@ public class TradeChatSession extends SupportSession {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean chatIsOpen() {
|
public boolean chatIsOpen() {
|
||||||
return trade != null && trade.getState() != Trade.State.WITHDRAW_COMPLETED;
|
return trade != null && trade.getState() != Trade.State.TRADE_COMPLETED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -158,7 +158,7 @@ public class ClosedTradableFormatter {
|
||||||
|
|
||||||
if (isBisqV1Trade(tradable)) {
|
if (isBisqV1Trade(tradable)) {
|
||||||
Trade trade = castToTrade(tradable);
|
Trade trade = castToTrade(tradable);
|
||||||
if (trade.isWithdrawn() || trade.isPayoutPublished()) {
|
if (trade.isCompleted() || trade.isPayoutPublished()) {
|
||||||
return Res.get("portfolio.closed.completed");
|
return Res.get("portfolio.closed.completed");
|
||||||
} else if (trade.getDisputeState() == DISPUTE_CLOSED) {
|
} else if (trade.getDisputeState() == DISPUTE_CLOSED) {
|
||||||
return Res.get("portfolio.closed.ticketClosed");
|
return Res.get("portfolio.closed.ticketClosed");
|
||||||
|
|
|
@ -18,24 +18,24 @@
|
||||||
package bisq.core.trade;
|
package bisq.core.trade;
|
||||||
|
|
||||||
import bisq.common.config.Config;
|
import bisq.common.config.Config;
|
||||||
import bisq.common.crypto.KeyRing;
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.crypto.Sig;
|
import bisq.common.crypto.Sig;
|
||||||
import bisq.common.util.Tuple2;
|
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferPayload;
|
import bisq.core.offer.OfferPayload;
|
||||||
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
import bisq.core.support.dispute.arbitration.arbitrator.Arbitrator;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.util.JsonUtil;
|
import bisq.core.util.JsonUtil;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.Objects;
|
import java.util.Collection;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collection of utilities for trading.
|
* Collection of utilities.
|
||||||
*/
|
*/
|
||||||
public class TradeUtils {
|
public class HavenoUtils {
|
||||||
|
|
||||||
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
public static final String LOOPBACK_HOST = "127.0.0.1"; // local loopback address to host Monero node
|
||||||
public static final String LOCALHOST = "localhost";
|
public static final String LOCALHOST = "localhost";
|
||||||
|
@ -148,61 +148,6 @@ public class TradeUtils {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): remove the following utitilites?
|
|
||||||
|
|
||||||
// Returns <MULTI_SIG, TRADE_PAYOUT> if both are AVAILABLE, otherwise null
|
|
||||||
static Tuple2<String, String> getAvailableAddresses(Trade trade, XmrWalletService xmrWalletService,
|
|
||||||
KeyRing keyRing) {
|
|
||||||
var addresses = getTradeAddresses(trade, xmrWalletService, keyRing);
|
|
||||||
if (addresses == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
if (xmrWalletService.getAvailableAddressEntries().stream()
|
|
||||||
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.first)))
|
|
||||||
return null;
|
|
||||||
if (xmrWalletService.getAvailableAddressEntries().stream()
|
|
||||||
.noneMatch(e -> Objects.equals(e.getAddressString(), addresses.second)))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return new Tuple2<>(addresses.first, addresses.second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns <MULTI_SIG, TRADE_PAYOUT> addresses as strings if they're known by the wallet
|
|
||||||
public static Tuple2<String, String> getTradeAddresses(Trade trade, XmrWalletService xmrWalletService,
|
|
||||||
KeyRing keyRing) {
|
|
||||||
var contract = trade.getContract();
|
|
||||||
if (contract == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
// TODO (woodser): xmr multisig does not use pub key
|
|
||||||
throw new RuntimeException("need to replace btc multisig pub key with xmr");
|
|
||||||
|
|
||||||
// Get multisig address
|
|
||||||
// var isMyRoleBuyer = contract.isMyRoleBuyer(keyRing.getPubKeyRing());
|
|
||||||
// var multiSigPubKey = isMyRoleBuyer ? contract.getBuyerMultiSigPubKey() : contract.getSellerMultiSigPubKey();
|
|
||||||
// if (multiSigPubKey == null)
|
|
||||||
// return null;
|
|
||||||
// var multiSigPubKeyString = Utilities.bytesAsHexString(multiSigPubKey);
|
|
||||||
// var multiSigAddress = xmrWalletService.getAddressEntryListAsImmutableList().stream()
|
|
||||||
// .filter(e -> e.getKeyPair().getPublicKeyAsHex().equals(multiSigPubKeyString))
|
|
||||||
// .findAny()
|
|
||||||
// .orElse(null);
|
|
||||||
// if (multiSigAddress == null)
|
|
||||||
// return null;
|
|
||||||
//
|
|
||||||
// // Get payout address
|
|
||||||
// var payoutAddress = isMyRoleBuyer ?
|
|
||||||
// contract.getBuyerPayoutAddressString() : contract.getSellerPayoutAddressString();
|
|
||||||
// var payoutAddressEntry = xmrWalletService.getAddressEntryListAsImmutableList().stream()
|
|
||||||
// .filter(e -> Objects.equals(e.getAddressString(), payoutAddress))
|
|
||||||
// .findAny()
|
|
||||||
// .orElse(null);
|
|
||||||
// if (payoutAddressEntry == null)
|
|
||||||
// return null;
|
|
||||||
//
|
|
||||||
// return new Tuple2<>(multiSigAddress.getAddressString(), payoutAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void awaitLatch(CountDownLatch latch) {
|
public static void awaitLatch(CountDownLatch latch) {
|
||||||
try {
|
try {
|
||||||
|
@ -211,4 +156,17 @@ public class TradeUtils {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void awaitTasks(Collection<Runnable> tasks) {
|
||||||
|
if (tasks.isEmpty()) return;
|
||||||
|
ExecutorService pool = Executors.newFixedThreadPool(tasks.size());
|
||||||
|
for (Runnable task : tasks) pool.submit(task);
|
||||||
|
pool.shutdown();
|
||||||
|
try {
|
||||||
|
if (!pool.awaitTermination(60000, TimeUnit.SECONDS)) pool.shutdownNow();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
pool.shutdownNow();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -50,9 +50,9 @@ import bisq.common.util.Utilities;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
import common.utils.GenUtils;
|
|
||||||
import org.bitcoinj.core.Coin;
|
import org.bitcoinj.core.Coin;
|
||||||
import org.bitcoinj.core.Transaction;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
import org.fxmisc.easybind.Subscription;
|
||||||
|
|
||||||
import javafx.beans.property.IntegerProperty;
|
import javafx.beans.property.IntegerProperty;
|
||||||
import javafx.beans.property.ObjectProperty;
|
import javafx.beans.property.ObjectProperty;
|
||||||
|
@ -88,13 +88,14 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
import monero.common.MoneroError;
|
import monero.common.MoneroError;
|
||||||
|
import monero.common.MoneroRpcConnection;
|
||||||
|
import monero.common.TaskLooper;
|
||||||
import monero.daemon.MoneroDaemon;
|
import monero.daemon.MoneroDaemon;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroCheckTx;
|
|
||||||
import monero.wallet.model.MoneroDestination;
|
import monero.wallet.model.MoneroDestination;
|
||||||
import monero.wallet.model.MoneroMultisigSignResult;
|
import monero.wallet.model.MoneroMultisigSignResult;
|
||||||
import monero.wallet.model.MoneroTransferQuery;
|
import monero.wallet.model.MoneroOutputWallet;
|
||||||
import monero.wallet.model.MoneroTxConfig;
|
import monero.wallet.model.MoneroTxConfig;
|
||||||
import monero.wallet.model.MoneroTxQuery;
|
import monero.wallet.model.MoneroTxQuery;
|
||||||
import monero.wallet.model.MoneroTxSet;
|
import monero.wallet.model.MoneroTxSet;
|
||||||
|
@ -125,9 +126,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
// deposit requested
|
// deposit requested
|
||||||
SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
SENT_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
||||||
SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
|
||||||
STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED), // not a mailbox msg, not used... remove
|
|
||||||
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
||||||
|
SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST(Phase.DEPOSIT_REQUESTED),
|
||||||
|
|
||||||
// deposit published
|
// deposit published
|
||||||
ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
|
ARBITRATOR_PUBLISHED_DEPOSIT_TXS(Phase.DEPOSITS_PUBLISHED),
|
||||||
|
@ -142,30 +142,20 @@ public abstract class Trade implements Tradable, Model {
|
||||||
// payment sent
|
// payment sent
|
||||||
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT(Phase.PAYMENT_SENT),
|
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT(Phase.PAYMENT_SENT),
|
||||||
BUYER_SENT_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
BUYER_SENT_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
||||||
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
|
||||||
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
|
||||||
BUYER_SEND_FAILED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
BUYER_SEND_FAILED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
||||||
|
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
||||||
|
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
||||||
SELLER_RECEIVED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
SELLER_RECEIVED_PAYMENT_SENT_MSG(Phase.PAYMENT_SENT),
|
||||||
|
|
||||||
// payment received
|
// payment received
|
||||||
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT(Phase.PAYMENT_RECEIVED),
|
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT(Phase.PAYMENT_RECEIVED),
|
||||||
SELLER_SENT_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
SELLER_SENT_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
|
||||||
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
|
||||||
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||||
|
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||||
// payout published
|
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG(Phase.PAYMENT_RECEIVED),
|
||||||
SELLER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED), // TODO (woodser): this enum is over used, like during arbitration
|
|
||||||
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
|
||||||
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
|
||||||
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
|
||||||
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
|
||||||
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG(Phase.PAYOUT_PUBLISHED),
|
|
||||||
BUYER_PUBLISHED_PAYOUT_TX(Phase.PAYOUT_PUBLISHED),
|
|
||||||
PAYOUT_TX_SEEN_IN_NETWORK(Phase.PAYOUT_PUBLISHED),
|
|
||||||
|
|
||||||
// trade completed
|
// trade completed
|
||||||
WITHDRAW_COMPLETED(Phase.WITHDRAWN);
|
TRADE_COMPLETED(Phase.COMPLETED);
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public Phase getPhase() {
|
public Phase getPhase() {
|
||||||
|
@ -199,14 +189,13 @@ public abstract class Trade implements Tradable, Model {
|
||||||
|
|
||||||
public enum Phase {
|
public enum Phase {
|
||||||
INIT,
|
INIT,
|
||||||
DEPOSIT_REQUESTED, // TODO (woodser): remove unused phases
|
DEPOSIT_REQUESTED,
|
||||||
DEPOSITS_PUBLISHED,
|
DEPOSITS_PUBLISHED,
|
||||||
DEPOSITS_CONFIRMED,
|
DEPOSITS_CONFIRMED,
|
||||||
DEPOSITS_UNLOCKED,
|
DEPOSITS_UNLOCKED,
|
||||||
PAYMENT_SENT,
|
PAYMENT_SENT,
|
||||||
PAYMENT_RECEIVED,
|
PAYMENT_RECEIVED,
|
||||||
PAYOUT_PUBLISHED,
|
COMPLETED;
|
||||||
WITHDRAWN;
|
|
||||||
|
|
||||||
public static Trade.Phase fromProto(protobuf.Trade.Phase phase) {
|
public static Trade.Phase fromProto(protobuf.Trade.Phase phase) {
|
||||||
return ProtoUtil.enumFromProto(Trade.Phase.class, phase.name());
|
return ProtoUtil.enumFromProto(Trade.Phase.class, phase.name());
|
||||||
|
@ -224,6 +213,25 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum PayoutState {
|
||||||
|
UNPUBLISHED,
|
||||||
|
PUBLISHED,
|
||||||
|
CONFIRMED,
|
||||||
|
UNLOCKED;
|
||||||
|
|
||||||
|
public static Trade.PayoutState fromProto(protobuf.Trade.PayoutState state) {
|
||||||
|
return ProtoUtil.enumFromProto(Trade.PayoutState.class, state.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static protobuf.Trade.PayoutState toProtoMessage(Trade.PayoutState state) {
|
||||||
|
return protobuf.Trade.PayoutState.valueOf(state.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isValidTransitionTo(PayoutState newState) {
|
||||||
|
return newState.ordinal() > this.ordinal();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum DisputeState {
|
public enum DisputeState {
|
||||||
NO_DISPUTE,
|
NO_DISPUTE,
|
||||||
// arbitration
|
// arbitration
|
||||||
|
@ -307,7 +315,6 @@ public abstract class Trade implements Tradable, Model {
|
||||||
private long takeOfferDate;
|
private long takeOfferDate;
|
||||||
|
|
||||||
// Mutable
|
// Mutable
|
||||||
@Nullable
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
private long amountAsLong;
|
private long amountAsLong;
|
||||||
|
@ -317,6 +324,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
@Getter
|
@Getter
|
||||||
private State state = State.PREPARATION;
|
private State state = State.PREPARATION;
|
||||||
@Getter
|
@Getter
|
||||||
|
private PayoutState payoutState = PayoutState.UNPUBLISHED;
|
||||||
|
@Getter
|
||||||
private DisputeState disputeState = DisputeState.NO_DISPUTE;
|
private DisputeState disputeState = DisputeState.NO_DISPUTE;
|
||||||
@Getter
|
@Getter
|
||||||
private TradePeriodState periodState = TradePeriodState.FIRST_HALF;
|
private TradePeriodState periodState = TradePeriodState.FIRST_HALF;
|
||||||
|
@ -351,11 +360,17 @@ public abstract class Trade implements Tradable, Model {
|
||||||
transient final private XmrWalletService xmrWalletService;
|
transient final private XmrWalletService xmrWalletService;
|
||||||
|
|
||||||
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
|
transient final private ObjectProperty<State> stateProperty = new SimpleObjectProperty<>(state);
|
||||||
transient final private ObjectProperty<Phase> statePhaseProperty = new SimpleObjectProperty<>(state.phase);
|
transient final private ObjectProperty<Phase> phaseProperty = new SimpleObjectProperty<>(state.phase);
|
||||||
|
transient final private ObjectProperty<PayoutState> payoutStateProperty = new SimpleObjectProperty<>(payoutState);
|
||||||
transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState);
|
transient final private ObjectProperty<DisputeState> disputeStateProperty = new SimpleObjectProperty<>(disputeState);
|
||||||
transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState);
|
transient final private ObjectProperty<TradePeriodState> tradePeriodStateProperty = new SimpleObjectProperty<>(periodState);
|
||||||
transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
|
transient final private StringProperty errorMessageProperty = new SimpleStringProperty();
|
||||||
|
transient private Subscription tradePhaseSubscription = null;
|
||||||
|
transient private Subscription payoutStateSubscription = null;
|
||||||
|
transient private TaskLooper tradeTxsLooper;
|
||||||
|
transient private Long lastWalletRefreshPeriod;
|
||||||
|
private static final long IDLE_SYNC_PERIOD_MS = 3600000; // 1 hour
|
||||||
|
|
||||||
// Mutable
|
// Mutable
|
||||||
@Getter
|
@Getter
|
||||||
transient private boolean isInitialized;
|
transient private boolean isInitialized;
|
||||||
|
@ -530,6 +545,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
.setAmountAsLong(amountAsLong)
|
.setAmountAsLong(amountAsLong)
|
||||||
.setPrice(price)
|
.setPrice(price)
|
||||||
.setState(Trade.State.toProtoMessage(state))
|
.setState(Trade.State.toProtoMessage(state))
|
||||||
|
.setPayoutState(Trade.PayoutState.toProtoMessage(payoutState))
|
||||||
.setDisputeState(Trade.DisputeState.toProtoMessage(disputeState))
|
.setDisputeState(Trade.DisputeState.toProtoMessage(disputeState))
|
||||||
.setPeriodState(Trade.TradePeriodState.toProtoMessage(periodState))
|
.setPeriodState(Trade.TradePeriodState.toProtoMessage(periodState))
|
||||||
.addAllChatMessage(chatMessages.stream()
|
.addAllChatMessage(chatMessages.stream()
|
||||||
|
@ -556,6 +572,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolver coreProtoResolver) {
|
public static Trade fromProto(Trade trade, protobuf.Trade proto, CoreProtoResolver coreProtoResolver) {
|
||||||
trade.setTakeOfferDate(proto.getTakeOfferDate());
|
trade.setTakeOfferDate(proto.getTakeOfferDate());
|
||||||
trade.setState(State.fromProto(proto.getState()));
|
trade.setState(State.fromProto(proto.getState()));
|
||||||
|
trade.setPayoutState(PayoutState.fromProto(proto.getPayoutState()));
|
||||||
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
|
trade.setDisputeState(DisputeState.fromProto(proto.getDisputeState()));
|
||||||
trade.setPeriodState(TradePeriodState.fromProto(proto.getPeriodState()));
|
trade.setPeriodState(TradePeriodState.fromProto(proto.getPeriodState()));
|
||||||
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
|
trade.setPayoutTxId(ProtoUtil.stringOrNullFromProto(proto.getPayoutTxId()));
|
||||||
|
@ -590,7 +607,56 @@ public abstract class Trade implements Tradable, Model {
|
||||||
getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
|
getArbitrator().setPubKeyRing(arbitrator.getPubKeyRing());
|
||||||
});
|
});
|
||||||
|
|
||||||
isInitialized = true;
|
isInitialized = true; // TODO: move to end?
|
||||||
|
|
||||||
|
// listen to daemon connection
|
||||||
|
xmrWalletService.getConnectionsService().addListener(newConnection -> setDaemonConnection(newConnection));
|
||||||
|
|
||||||
|
// done if payout unlocked
|
||||||
|
if (isPayoutUnlocked()) return;
|
||||||
|
|
||||||
|
// handle trade state events
|
||||||
|
if (isDepositPublished()) listenToTradeTxs();
|
||||||
|
tradePhaseSubscription = EasyBind.subscribe(phaseProperty, newValue -> {
|
||||||
|
updateTxListenerRefreshPeriod();
|
||||||
|
if (isDepositPublished()) listenToTradeTxs();
|
||||||
|
if (isCompleted()) {
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
if (tradePhaseSubscription != null) {
|
||||||
|
tradePhaseSubscription.unsubscribe();
|
||||||
|
tradePhaseSubscription = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handle payout state events
|
||||||
|
payoutStateSubscription = EasyBind.subscribe(payoutStateProperty, newValue -> {
|
||||||
|
updateTxListenerRefreshPeriod();
|
||||||
|
|
||||||
|
// cleanup when payout published
|
||||||
|
if (isPayoutPublished()) {
|
||||||
|
log.info("Payout published for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
if (isArbitrator() && !isCompleted()) processModel.getTradeManager().onTradeCompleted(this); // complete arbitrator trade when payout published
|
||||||
|
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup when payout unlocks
|
||||||
|
if (isPayoutUnlocked()) {
|
||||||
|
log.info("Payout unlocked for {} {}, deleting multisig wallet", getClass().getSimpleName(), getId()); // TODO: retain backup for some time?
|
||||||
|
deleteWallet();
|
||||||
|
if (tradeTxsLooper != null) {
|
||||||
|
tradeTxsLooper.stop();
|
||||||
|
tradeTxsLooper = null;
|
||||||
|
}
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
if (payoutStateSubscription != null) {
|
||||||
|
payoutStateSubscription.unsubscribe();
|
||||||
|
payoutStateSubscription = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -603,12 +669,12 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeAddress getTradingPeerNodeAddress() {
|
public NodeAddress getTradingPeerNodeAddress() {
|
||||||
return getTradingPeer() == null ? null : getTradingPeer().getNodeAddress();
|
return getTradingPeer() == null ? null : getTradingPeer().getNodeAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public NodeAddress getArbitratorNodeAddress() {
|
public NodeAddress getArbitratorNodeAddress() {
|
||||||
return getArbitrator() == null ? null : getArbitrator().getNodeAddress();
|
return getArbitrator() == null ? null : getArbitrator().getNodeAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a contract based on the current state.
|
* Create a contract based on the current state.
|
||||||
|
@ -761,9 +827,8 @@ public abstract class Trade implements Tradable, Model {
|
||||||
// submit payout tx
|
// submit payout tx
|
||||||
if (publish) {
|
if (publish) {
|
||||||
multisigWallet.submitMultisigTxHex(payoutTxHex);
|
multisigWallet.submitMultisigTxHex(payoutTxHex);
|
||||||
setState(isArbitrator() ? Trade.State.WITHDRAW_COMPLETED : isBuyer() ? Trade.State.BUYER_PUBLISHED_PAYOUT_TX : Trade.State.SELLER_PUBLISHED_PAYOUT_TX);
|
setPayoutState(Trade.PayoutState.PUBLISHED);
|
||||||
}
|
}
|
||||||
walletService.closeMultisigWallet(getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -771,7 +836,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
*
|
*
|
||||||
* @param paymentAccountKey is the key to decrypt the payment account payload
|
* @param paymentAccountKey is the key to decrypt the payment account payload
|
||||||
*/
|
*/
|
||||||
public void decryptPeersPaymentAccountPayload(byte[] paymentAccountKey) {
|
public void decryptPeerPaymentAccountPayload(byte[] paymentAccountKey) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// decrypt payment account payload
|
// decrypt payment account payload
|
||||||
|
@ -792,139 +857,6 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Listen for deposit transactions to unlock and then apply the transactions.
|
|
||||||
*
|
|
||||||
* TODO: adopt for general purpose scheduling
|
|
||||||
* TODO: check and notify if deposits are dropped due to re-org
|
|
||||||
*/
|
|
||||||
public void listenForDepositTxs() {
|
|
||||||
log.info("Listening for deposit txs to unlock for trade {}", getId());
|
|
||||||
|
|
||||||
// ignore if already listening
|
|
||||||
if (depositTxListener != null) {
|
|
||||||
log.warn("Trade {} already listening for deposit txs", getId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get daemon and primary wallet
|
|
||||||
MoneroWallet havenoWallet = processModel.getXmrWalletService().getWallet();
|
|
||||||
|
|
||||||
// fetch deposit txs from daemon
|
|
||||||
List<MoneroTx> txs = xmrWalletService.getTxs(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
|
||||||
|
|
||||||
// handle deposit txs seen
|
|
||||||
if (txs.size() == 2) {
|
|
||||||
setStateDepositsPublished();
|
|
||||||
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
|
|
||||||
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
|
|
||||||
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
|
|
||||||
|
|
||||||
// check if deposit txs unlocked
|
|
||||||
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) {
|
|
||||||
setStateDepositsConfirmed();
|
|
||||||
long unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
|
|
||||||
if (havenoWallet.getHeight() >= unlockHeight) {
|
|
||||||
setStateDepositsUnlocked();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create block listener
|
|
||||||
depositTxListener = new MoneroWalletListener() {
|
|
||||||
Long unlockHeight = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNewBlock(long height) {
|
|
||||||
|
|
||||||
// skip if no longer listening
|
|
||||||
if (depositTxListener == null) return;
|
|
||||||
|
|
||||||
// use latest height
|
|
||||||
height = havenoWallet.getHeight();
|
|
||||||
|
|
||||||
// skip if before unlock height
|
|
||||||
if (unlockHeight != null && height < unlockHeight) return;
|
|
||||||
|
|
||||||
// fetch txs from daemon
|
|
||||||
List<MoneroTx> txs = xmrWalletService.getTxs(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()));
|
|
||||||
|
|
||||||
// skip if deposit txs not seen
|
|
||||||
if (txs.size() != 2) return;
|
|
||||||
setStateDepositsPublished();
|
|
||||||
|
|
||||||
// update deposit txs
|
|
||||||
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
|
|
||||||
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
|
|
||||||
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
|
|
||||||
|
|
||||||
// check if deposit txs confirmed and compute unlock height
|
|
||||||
if (txs.size() == 2 && txs.get(0).isConfirmed() && txs.get(1).isConfirmed() && unlockHeight == null) {
|
|
||||||
log.info("Multisig deposits confirmed for trade {}", getId());
|
|
||||||
setStateDepositsConfirmed();
|
|
||||||
unlockHeight = Math.max(txs.get(0).getHeight(), txs.get(1).getHeight()) + XmrWalletService.NUM_BLOCKS_UNLOCK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if deposit txs unlocked
|
|
||||||
if (unlockHeight != null && height >= unlockHeight) {
|
|
||||||
log.info("Multisig deposits unlocked for trade {}", getId());
|
|
||||||
xmrWalletService.removeWalletListener(depositTxListener); // remove listener when notified
|
|
||||||
depositTxListener = null; // prevent re-applying trade state in subsequent requests
|
|
||||||
setStateDepositsUnlocked();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// register wallet listener
|
|
||||||
xmrWalletService.addWalletListener(depositTxListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void listenForPayoutTx() {
|
|
||||||
log.info("Listening for payout tx for trade {}", getId());
|
|
||||||
|
|
||||||
// check if payout tx already seen
|
|
||||||
if (getState().ordinal() >= Trade.State.PAYOUT_TX_SEEN_IN_NETWORK.ordinal()) {
|
|
||||||
log.warn("We had a payout tx already set. tradeId={}, state={}", getId(), getState());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get payout address entry
|
|
||||||
Optional<XmrAddressEntry> optionalPayoutEntry = xmrWalletService.getAddressEntry(getId(), XmrAddressEntry.Context.TRADE_PAYOUT);
|
|
||||||
if (!optionalPayoutEntry.isPresent()) throw new RuntimeException("Trade does not have address entry for payout");
|
|
||||||
XmrAddressEntry payoutEntry = optionalPayoutEntry.get();
|
|
||||||
|
|
||||||
// watch for payout tx on loop
|
|
||||||
new Thread(() -> { // TODO: use thread manager
|
|
||||||
boolean found = false;
|
|
||||||
while (!found) {
|
|
||||||
if (getPayoutTxKey() != null) {
|
|
||||||
|
|
||||||
// get txs to payout address
|
|
||||||
List<MoneroTxWallet> txs = xmrWalletService.getWallet().getTxs(new MoneroTxQuery()
|
|
||||||
.setTransferQuery(new MoneroTransferQuery()
|
|
||||||
.setAccountIndex(0)
|
|
||||||
.setSubaddressIndex(payoutEntry.getSubaddressIndex())
|
|
||||||
.setIsIncoming(true)));
|
|
||||||
|
|
||||||
// check for payout tx
|
|
||||||
for (MoneroTxWallet tx : txs) {
|
|
||||||
MoneroCheckTx txCheck = xmrWalletService.getWallet().checkTxKey(tx.getHash(), getPayoutTxKey(), payoutEntry.getAddressString());
|
|
||||||
if (txCheck.isGood() && txCheck.receivedAmount.compareTo(new BigInteger("0")) > 0) {
|
|
||||||
found = true;
|
|
||||||
setPayoutTx(tx);
|
|
||||||
setStateIfValidTransitionTo(Trade.State.PAYOUT_TX_SEEN_IN_NETWORK);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait to loop
|
|
||||||
GenUtils.waitFor(xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs());
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public MoneroTx getTakerDepositTx() {
|
public MoneroTx getTakerDepositTx() {
|
||||||
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
|
String depositTxHash = getProcessModel().getTaker().getDepositTxHash();
|
||||||
|
@ -984,6 +916,30 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public MoneroWallet getWallet() {
|
||||||
|
return xmrWalletService.multisigWalletExists(getId()) ? xmrWalletService.getMultisigWallet(getId()) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void syncWallet() {
|
||||||
|
log.info("Syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
getWallet().sync();
|
||||||
|
log.info("Done syncing wallet for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
pollWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveWallet() {
|
||||||
|
xmrWalletService.saveWallet(getWallet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteWallet() {
|
||||||
|
if (xmrWalletService.multisigWalletExists(getId())) xmrWalletService.deleteMultisigWallet(getId());
|
||||||
|
else log.warn("Multisig wallet to delete for trade {} does not exist", getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
isInitialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Model implementation
|
// Model implementation
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -1055,7 +1011,37 @@ public abstract class Trade implements Tradable, Model {
|
||||||
this.state = state;
|
this.state = state;
|
||||||
UserThread.execute(() -> {
|
UserThread.execute(() -> {
|
||||||
stateProperty.set(state);
|
stateProperty.set(state);
|
||||||
statePhaseProperty.set(state.getPhase());
|
phaseProperty.set(state.getPhase());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStateIfProgress(State state) {
|
||||||
|
if (state.ordinal() > getState().ordinal()) setState(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayoutStateIfValidTransitionTo(PayoutState newPayoutState) {
|
||||||
|
if (payoutState.isValidTransitionTo(newPayoutState)) {
|
||||||
|
setPayoutState(newPayoutState);
|
||||||
|
} else {
|
||||||
|
log.warn("Payout state change is not getting applied because it would cause an invalid transition. " +
|
||||||
|
"Trade payout state={}, intended payout state={}", payoutState, newPayoutState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPayoutState(PayoutState payoutState) {
|
||||||
|
if (isInitialized) {
|
||||||
|
// We don't want to log at startup the setState calls from all persisted trades
|
||||||
|
log.info("Set new payout state at {} (id={}): {}", this.getClass().getSimpleName(), getShortId(), payoutState);
|
||||||
|
}
|
||||||
|
if (payoutState.ordinal() < this.payoutState.ordinal()) {
|
||||||
|
String message = "We got a payout state change to a previous phase (id=" + getShortId() + ").\n" +
|
||||||
|
"Old payout state is: " + this.state + ". New payout state is: " + payoutState;
|
||||||
|
log.warn(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.payoutState = payoutState;
|
||||||
|
UserThread.execute(() -> {
|
||||||
|
payoutStateProperty.set(payoutState);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1264,7 +1250,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return getState().getPhase().ordinal() == Phase.INIT.ordinal();
|
return getState().getPhase().ordinal() == Phase.INIT.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTakerFeePublished() {
|
public boolean isDepositRequested() {
|
||||||
return getState().getPhase().ordinal() >= Phase.DEPOSIT_REQUESTED.ordinal();
|
return getState().getPhase().ordinal() >= Phase.DEPOSIT_REQUESTED.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1319,16 +1305,20 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
return getState().getPhase().ordinal() >= Phase.PAYMENT_RECEIVED.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isPayoutPublished() {
|
|
||||||
return getState().getPhase().ordinal() >= Phase.PAYOUT_PUBLISHED.ordinal() || isWithdrawn();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isCompleted() {
|
public boolean isCompleted() {
|
||||||
return isPayoutPublished();
|
return getState().getPhase().ordinal() >= Phase.COMPLETED.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isWithdrawn() {
|
public boolean isPayoutPublished() {
|
||||||
return getState().getPhase().ordinal() == Phase.WITHDRAWN.ordinal();
|
return getPayoutState().ordinal() >= PayoutState.PUBLISHED.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPayoutConfirmed() {
|
||||||
|
return getPayoutState().ordinal() >= PayoutState.CONFIRMED.ordinal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPayoutUnlocked() {
|
||||||
|
return getPayoutState().ordinal() >= PayoutState.UNLOCKED.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<State> stateProperty() {
|
public ReadOnlyObjectProperty<State> stateProperty() {
|
||||||
|
@ -1336,7 +1326,11 @@ public abstract class Trade implements Tradable, Model {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<Phase> statePhaseProperty() {
|
public ReadOnlyObjectProperty<Phase> statePhaseProperty() {
|
||||||
return statePhaseProperty;
|
return phaseProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ReadOnlyObjectProperty<PayoutState> payoutStateProperty() {
|
||||||
|
return payoutStateProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadOnlyObjectProperty<DisputeState> disputeStateProperty() {
|
public ReadOnlyObjectProperty<DisputeState> disputeStateProperty() {
|
||||||
|
@ -1439,6 +1433,98 @@ public abstract class Trade implements Tradable, Model {
|
||||||
return tradeVolumeProperty;
|
return tradeVolumeProperty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void listenToTradeTxs() {
|
||||||
|
if (tradeTxsLooper != null) return;
|
||||||
|
log.info("Listening for payout tx for {} {}", getClass().getSimpleName(), getId());
|
||||||
|
|
||||||
|
// poll wallet for tx state
|
||||||
|
pollWallet();
|
||||||
|
tradeTxsLooper = new TaskLooper(() -> {
|
||||||
|
try {
|
||||||
|
pollWallet();
|
||||||
|
} catch (Exception e) {
|
||||||
|
if (isInitialized) log.warn("Error checking trade txs in background: " + e.getMessage());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
tradeTxsLooper.start(getWalletRefreshPeriod());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pollWallet() {
|
||||||
|
|
||||||
|
// skip if payout unlocked
|
||||||
|
if (isPayoutUnlocked()) return;
|
||||||
|
|
||||||
|
// rescan spent if deposits unlocked
|
||||||
|
if (isDepositUnlocked()) getWallet().rescanSpent();
|
||||||
|
|
||||||
|
// get txs with outputs
|
||||||
|
List<MoneroTxWallet> txs = getWallet().getTxs(new MoneroTxQuery()
|
||||||
|
.setHashes(Arrays.asList(processModel.getMaker().getDepositTxHash(), processModel.getTaker().getDepositTxHash()))
|
||||||
|
.setIncludeOutputs(true));
|
||||||
|
|
||||||
|
// check deposit txs
|
||||||
|
if (!isDepositUnlocked()) {
|
||||||
|
if (txs.size() == 2) {
|
||||||
|
setStateDepositsPublished();
|
||||||
|
boolean makerFirst = txs.get(0).getHash().equals(processModel.getMaker().getDepositTxHash());
|
||||||
|
getMaker().setDepositTx(makerFirst ? txs.get(0) : txs.get(1));
|
||||||
|
getTaker().setDepositTx(makerFirst ? txs.get(1) : txs.get(0));
|
||||||
|
|
||||||
|
// check if deposit txs confirmed
|
||||||
|
if (txs.get(0).isConfirmed() && txs.get(1).isConfirmed()) setStateDepositsConfirmed();
|
||||||
|
if (!txs.get(0).isLocked() && !txs.get(1).isLocked()) setStateDepositsUnlocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check payout tx
|
||||||
|
else {
|
||||||
|
|
||||||
|
// check if deposit txs spent (appears on payout published)
|
||||||
|
for (MoneroTxWallet tx : txs) {
|
||||||
|
for (MoneroOutputWallet output : tx.getOutputsWallet()) {
|
||||||
|
if (Boolean.TRUE.equals(output.isSpent())) {
|
||||||
|
setPayoutStatePublished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for outgoing txs (appears on payout confirmed)
|
||||||
|
List<MoneroTxWallet> outgoingTxs = getWallet().getTxs(new MoneroTxQuery().setIsOutgoing(true));
|
||||||
|
if (!outgoingTxs.isEmpty()) {
|
||||||
|
MoneroTxWallet payoutTx = outgoingTxs.get(0);
|
||||||
|
setPayoutTx(payoutTx);
|
||||||
|
setPayoutStatePublished();
|
||||||
|
if (payoutTx.isConfirmed()) setPayoutStateConfirmed();
|
||||||
|
if (!payoutTx.isLocked()) setPayoutStateUnlocked();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setDaemonConnection(MoneroRpcConnection connection) {
|
||||||
|
log.info("Setting daemon connection for trade wallet {}: {}: ", getId() , connection == null ? null : connection.getUri());
|
||||||
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(getId());
|
||||||
|
multisigWallet.setDaemonConnection(connection);
|
||||||
|
multisigWallet.startSyncing(getWalletRefreshPeriod());
|
||||||
|
updateTxListenerRefreshPeriod();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTxListenerRefreshPeriod() {
|
||||||
|
long walletRefreshPeriod = getWalletRefreshPeriod();
|
||||||
|
if (lastWalletRefreshPeriod != null && lastWalletRefreshPeriod == walletRefreshPeriod) return;
|
||||||
|
log.info("Setting wallet refresh rate for {} to {}", getClass().getSimpleName(), walletRefreshPeriod);
|
||||||
|
lastWalletRefreshPeriod = walletRefreshPeriod;
|
||||||
|
if (tradeTxsLooper != null) {
|
||||||
|
tradeTxsLooper.stop();
|
||||||
|
tradeTxsLooper = null;
|
||||||
|
listenToTradeTxs();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private long getWalletRefreshPeriod() {
|
||||||
|
if (this instanceof ArbitratorTrade && isDepositConfirmed()) return IDLE_SYNC_PERIOD_MS; // arbitrator slows trade wallet after deposits confirm since messages are expected so this is only backup
|
||||||
|
return xmrWalletService.getConnectionsService().getDefaultRefreshPeriodMs(); // otherwise sync at default refresh rate
|
||||||
|
}
|
||||||
|
|
||||||
private void setStateDepositsPublished() {
|
private void setStateDepositsPublished() {
|
||||||
if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_NETWORK);
|
if (!isDepositPublished()) setState(State.DEPOSIT_TXS_SEEN_IN_NETWORK);
|
||||||
}
|
}
|
||||||
|
@ -1451,6 +1537,18 @@ public abstract class Trade implements Tradable, Model {
|
||||||
if (!isDepositUnlocked()) setState(State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
if (!isDepositUnlocked()) setState(State.DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setPayoutStatePublished() {
|
||||||
|
if (!isPayoutPublished()) setPayoutState(PayoutState.PUBLISHED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPayoutStateConfirmed() {
|
||||||
|
if (!isPayoutConfirmed()) setPayoutState(PayoutState.CONFIRMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPayoutStateUnlocked() {
|
||||||
|
if (!isPayoutUnlocked()) setPayoutState(PayoutState.UNLOCKED);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Trade{" +
|
return "Trade{" +
|
||||||
|
@ -1463,6 +1561,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
",\n tradeAmountAsLong=" + amountAsLong +
|
",\n tradeAmountAsLong=" + amountAsLong +
|
||||||
",\n tradePrice=" + price +
|
",\n tradePrice=" + price +
|
||||||
",\n state=" + state +
|
",\n state=" + state +
|
||||||
|
",\n payoutState=" + payoutState +
|
||||||
",\n disputeState=" + disputeState +
|
",\n disputeState=" + disputeState +
|
||||||
",\n tradePeriodState=" + periodState +
|
",\n tradePeriodState=" + periodState +
|
||||||
",\n contract=" + contract +
|
",\n contract=" + contract +
|
||||||
|
@ -1477,7 +1576,7 @@ public abstract class Trade implements Tradable, Model {
|
||||||
",\n takerFee=" + takerFee +
|
",\n takerFee=" + takerFee +
|
||||||
",\n xmrWalletService=" + xmrWalletService +
|
",\n xmrWalletService=" + xmrWalletService +
|
||||||
",\n stateProperty=" + stateProperty +
|
",\n stateProperty=" + stateProperty +
|
||||||
",\n statePhaseProperty=" + statePhaseProperty +
|
",\n statePhaseProperty=" + phaseProperty +
|
||||||
",\n disputeStateProperty=" + disputeStateProperty +
|
",\n disputeStateProperty=" + disputeStateProperty +
|
||||||
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
|
",\n tradePeriodStateProperty=" + tradePeriodStateProperty +
|
||||||
",\n errorMessageProperty=" + errorMessageProperty +
|
",\n errorMessageProperty=" + errorMessageProperty +
|
||||||
|
|
|
@ -41,10 +41,8 @@ import bisq.core.trade.messages.DepositRequest;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyRequest;
|
|
||||||
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.messages.UpdateMultisigRequest;
|
|
||||||
import bisq.core.trade.protocol.ArbitratorProtocol;
|
import bisq.core.trade.protocol.ArbitratorProtocol;
|
||||||
import bisq.core.trade.protocol.MakerProtocol;
|
import bisq.core.trade.protocol.MakerProtocol;
|
||||||
import bisq.core.trade.protocol.ProcessModel;
|
import bisq.core.trade.protocol.ProcessModel;
|
||||||
|
@ -66,7 +64,6 @@ import bisq.network.p2p.P2PService;
|
||||||
import bisq.network.p2p.network.TorNetworkNode;
|
import bisq.network.p2p.network.TorNetworkNode;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import bisq.common.ClockWatcher;
|
import bisq.common.ClockWatcher;
|
||||||
import bisq.common.config.Config;
|
|
||||||
import bisq.common.crypto.KeyRing;
|
import bisq.common.crypto.KeyRing;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.FaultHandler;
|
import bisq.common.handlers.FaultHandler;
|
||||||
|
@ -245,10 +242,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
handleDepositRequest((DepositRequest) networkEnvelope, peer);
|
handleDepositRequest((DepositRequest) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof DepositResponse) {
|
} else if (networkEnvelope instanceof DepositResponse) {
|
||||||
handleDepositResponse((DepositResponse) networkEnvelope, peer);
|
handleDepositResponse((DepositResponse) networkEnvelope, peer);
|
||||||
} else if (networkEnvelope instanceof PaymentAccountKeyRequest) {
|
|
||||||
handlePaymentAccountKeyRequest((PaymentAccountKeyRequest) networkEnvelope, peer);
|
|
||||||
} else if (networkEnvelope instanceof UpdateMultisigRequest) {
|
|
||||||
handleUpdateMultisigRequest((UpdateMultisigRequest) networkEnvelope, peer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,6 +277,26 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
thawUnreservedOutputs();
|
thawUnreservedOutputs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void shutDown() {
|
||||||
|
|
||||||
|
// collect trades to shutdown
|
||||||
|
Set<Trade> trades = new HashSet<Trade>();
|
||||||
|
trades.addAll(tradableList.getList());
|
||||||
|
trades.addAll(closedTradableManager.getClosedTrades());
|
||||||
|
trades.addAll(failedTradesManager.getObservableList());
|
||||||
|
|
||||||
|
// shut down trades in parallel
|
||||||
|
Set<Runnable> tasks = new HashSet<Runnable>();
|
||||||
|
for (Trade trade : trades) tasks.add(() -> {
|
||||||
|
try {
|
||||||
|
trade.shutDown();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Error closing trade subprocess. Was Haveno stopped manually with ctrl+c?");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
HavenoUtils.awaitTasks(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
private void thawUnreservedOutputs() {
|
private void thawUnreservedOutputs() {
|
||||||
if (xmrWalletService.getWallet() == null) return;
|
if (xmrWalletService.getWallet() == null) return;
|
||||||
|
|
||||||
|
@ -301,13 +314,15 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
Set<String> frozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery()
|
Set<String> frozenKeyImages = xmrWalletService.getWallet().getOutputs(new MoneroOutputQuery()
|
||||||
.setIsFrozen(true)
|
.setIsFrozen(true)
|
||||||
.setIsSpent(false))
|
.setIsSpent(false))
|
||||||
.stream().map(output -> output.getKeyImage().getHex())
|
.stream()
|
||||||
|
.map(output -> output.getKeyImage().getHex())
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
frozenKeyImages.removeAll(reservedKeyImages);
|
frozenKeyImages.removeAll(reservedKeyImages);
|
||||||
for (String unreservedFrozenKeyImage : frozenKeyImages) {
|
for (String unreservedFrozenKeyImage : frozenKeyImages) {
|
||||||
log.info("Thawing output which is not reserved for offer or trade: " + unreservedFrozenKeyImage);
|
log.info("Thawing output which is not reserved for offer or trade: " + unreservedFrozenKeyImage);
|
||||||
xmrWalletService.getWallet().thawOutput(unreservedFrozenKeyImage);
|
xmrWalletService.getWallet().thawOutput(unreservedFrozenKeyImage);
|
||||||
}
|
}
|
||||||
|
xmrWalletService.getWallet().save();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradeProtocol getTradeProtocol(Trade trade) {
|
public TradeProtocol getTradeProtocol(Trade trade) {
|
||||||
|
@ -369,7 +384,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
|
|
||||||
private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) {
|
private void initTradeAndProtocol(Trade trade, TradeProtocol tradeProtocol) {
|
||||||
tradeProtocol.initialize(processModelServiceProvider, this, trade.getOffer());
|
tradeProtocol.initialize(processModelServiceProvider, this, trade.getOffer());
|
||||||
trade.initialize(processModelServiceProvider);
|
|
||||||
requestPersistence(); // TODO requesting persistence twice with initPersistedTrade()
|
requestPersistence(); // TODO requesting persistence twice with initPersistedTrade()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -470,7 +484,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
|
|
||||||
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
((ArbitratorProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||||
log.warn("Arbitrator error during trade initialization for trade {}: {}", trade.getId(), errorMessage);
|
log.warn("Arbitrator error during trade initialization for trade {}: {}", trade.getId(), errorMessage);
|
||||||
maybeRemoveTrade(trade);
|
removeTrade(trade);
|
||||||
});
|
});
|
||||||
|
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
|
@ -555,7 +569,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
((MakerProtocol) getTradeProtocol(trade)).handleInitTradeRequest(request, sender, errorMessage -> {
|
||||||
log.warn("Maker error during trade initialization: " + errorMessage);
|
log.warn("Maker error during trade initialization: " + errorMessage);
|
||||||
openOfferManager.unreserveOpenOffer(openOffer); // offer remains available // TODO: only unreserve if funds not deposited to multisig
|
openOfferManager.unreserveOpenOffer(openOffer); // offer remains available // TODO: only unreserve if funds not deposited to multisig
|
||||||
maybeRemoveTrade(trade);
|
removeTrade(trade);
|
||||||
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
if (takeOfferRequestErrorMessageHandler != null) takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -658,45 +672,6 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer);
|
((TraderProtocol) getTradeProtocol(trade)).handleDepositResponse(response, peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress peer) {
|
|
||||||
log.info("Received PaymentAccountKeyRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
|
||||||
|
|
||||||
try {
|
|
||||||
Validator.nonEmptyStringOf(request.getTradeId());
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.warn("Invalid PaymentAccountKeyRequest message " + request.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<Trade> tradeOptional = getOpenTrade(request.getTradeId());
|
|
||||||
if (!tradeOptional.isPresent()) {
|
|
||||||
log.warn("No trade with id " + request.getTradeId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Trade trade = tradeOptional.get();
|
|
||||||
((ArbitratorProtocol) getTradeProtocol(trade)).handlePaymentAccountKeyRequest(request, peer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void handleUpdateMultisigRequest(UpdateMultisigRequest request, NodeAddress peer) {
|
|
||||||
log.info("Received UpdateMultisigRequest from {} with tradeId {} and uid {}", peer, request.getTradeId(), request.getUid());
|
|
||||||
|
|
||||||
try {
|
|
||||||
Validator.nonEmptyStringOf(request.getTradeId());
|
|
||||||
} catch (Throwable t) {
|
|
||||||
log.warn("Invalid UpdateMultisigRequest message " + request.toString());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Optional<Trade> tradeOptional = getOpenTrade(request.getTradeId());
|
|
||||||
if (!tradeOptional.isPresent()) throw new RuntimeException("No trade with id " + request.getTradeId()); // TODO (woodser): error handling
|
|
||||||
Trade trade = tradeOptional.get();
|
|
||||||
getTradeProtocol(trade).handleUpdateMultisigRequest(request, peer, errorMessage -> {
|
|
||||||
log.warn("Error handling UpdateMultisigRequest: " + errorMessage);
|
|
||||||
if (takeOfferRequestErrorMessageHandler != null)
|
|
||||||
takeOfferRequestErrorMessageHandler.handleErrorMessage(errorMessage);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Take offer
|
// Take offer
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -777,7 +752,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
}, errorMessage -> {
|
}, errorMessage -> {
|
||||||
log.warn("Taker error during trade initialization: " + errorMessage);
|
log.warn("Taker error during trade initialization: " + errorMessage);
|
||||||
errorMessageHandler.handleErrorMessage(errorMessage);
|
errorMessageHandler.handleErrorMessage(errorMessage);
|
||||||
maybeRemoveTrade(trade);
|
removeTrade(trade);
|
||||||
});
|
});
|
||||||
requestPersistence();
|
requestPersistence();
|
||||||
}
|
}
|
||||||
|
@ -830,8 +805,8 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
|
// If trade was completed (closed without fault but might be closed by a dispute) we move it to the closed trades
|
||||||
public void onTradeCompleted(Trade trade) {
|
public void onTradeCompleted(Trade trade) {
|
||||||
closedTradableManager.add(trade);
|
closedTradableManager.add(trade);
|
||||||
trade.setState(Trade.State.WITHDRAW_COMPLETED);
|
trade.setState(Trade.State.TRADE_COMPLETED);
|
||||||
maybeRemoveTrade(trade);
|
removeTrade(trade);
|
||||||
|
|
||||||
// TODO The address entry should have been removed already. Check and if its the case remove that.
|
// TODO The address entry should have been removed already. Check and if its the case remove that.
|
||||||
xmrWalletService.resetAddressEntriesForPendingTrade(trade.getId());
|
xmrWalletService.resetAddressEntriesForPendingTrade(trade.getId());
|
||||||
|
@ -899,7 +874,7 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
// If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published)
|
// If trade is in already in critical state (if taker role: taker fee; both roles: after deposit published)
|
||||||
// we move the trade to failedTradesManager
|
// we move the trade to failedTradesManager
|
||||||
public void onMoveInvalidTradeToFailedTrades(Trade trade) {
|
public void onMoveInvalidTradeToFailedTrades(Trade trade) {
|
||||||
maybeRemoveTrade(trade);
|
removeTrade(trade);
|
||||||
failedTradesManager.add(trade);
|
failedTradesManager.add(trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1050,34 +1025,28 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
return closedTradableManager.getClosedTrades().stream().filter(e -> e.getId().equals(tradeId)).findFirst();
|
return closedTradableManager.getClosedTrades().stream().filter(e -> e.getId().equals(tradeId)).findFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized void maybeRemoveTrade(Trade trade) {
|
private synchronized void removeTrade(Trade trade) {
|
||||||
log.info("TradeManager.maybeRemoveTrade()");
|
log.info("TradeManager.removeTrade()");
|
||||||
synchronized(tradableList) {
|
synchronized(tradableList) {
|
||||||
if (!tradableList.contains(trade)) return;
|
if (!tradableList.contains(trade)) return;
|
||||||
|
|
||||||
// delete trade if not possibly funded
|
// remove trade
|
||||||
if (trade.getPhase().ordinal() < Trade.Phase.DEPOSIT_REQUESTED.ordinal() || trade.getPhase().ordinal() >= Trade.Phase.PAYOUT_PUBLISHED.ordinal()) { // TODO: delete after payout unlocked
|
tradableList.remove(trade);
|
||||||
|
|
||||||
// remove trade
|
// unreserve trade key images
|
||||||
tradableList.remove(trade);
|
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
|
||||||
|
for (String keyImage : trade.getSelf().getReserveTxKeyImages()) xmrWalletService.getWallet().thawOutput(keyImage);
|
||||||
// unreserve trade key images
|
xmrWalletService.getWallet().save();
|
||||||
if (trade instanceof TakerTrade && trade.getSelf().getReserveTxKeyImages() != null) {
|
|
||||||
for (String keyImage : trade.getSelf().getReserveTxKeyImages()) {
|
|
||||||
xmrWalletService.getWallet().thawOutput(keyImage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete multisig wallet
|
|
||||||
deleteTradeWallet(trade);
|
|
||||||
|
|
||||||
// unregister and persist
|
|
||||||
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
|
||||||
requestPersistence();
|
|
||||||
} else {
|
|
||||||
log.warn("Not deleting trade " + trade.getId() + " because its trade wallet might be funded");
|
|
||||||
// TODO: schedule wallet for deletion after unlock
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// delete trade wallet if before funded
|
||||||
|
if (xmrWalletService.multisigWalletExists(trade.getId()) && !trade.isDepositRequested()) {
|
||||||
|
trade.deleteWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// unregister and persist
|
||||||
|
p2PService.removeDecryptedDirectMessageListener(getTradeProtocol(trade));
|
||||||
|
requestPersistence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1094,9 +1063,4 @@ public class TradeManager implements PersistedDataHost, DecryptedDirectMessageLi
|
||||||
private void onTradesChanged() {
|
private void onTradesChanged() {
|
||||||
this.numPendingTrades.set(getObservableList().size());
|
this.numPendingTrades.set(getObservableList().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteTradeWallet(Trade trade) {
|
|
||||||
if (xmrWalletService.multisigWalletExists(trade.getId())) xmrWalletService.deleteMultisigWallet(trade.getId());
|
|
||||||
else log.warn("Multisig wallet to delete for trade {} does not exist", trade.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,6 @@ import bisq.core.proto.CoreProtoResolver;
|
||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
import bisq.network.p2p.DirectMessage;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import com.google.protobuf.ByteString;
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
|
@ -24,6 +24,8 @@ import bisq.network.p2p.NodeAddress;
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import bisq.common.app.Version;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.proto.ProtoUtil;
|
import bisq.common.proto.ProtoUtil;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -31,25 +33,24 @@ import lombok.Value;
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Value
|
@Value
|
||||||
public final class PaymentAccountKeyResponse extends TradeMailboxMessage implements DirectMessage {
|
public final class DepositsConfirmedMessage extends TradeMailboxMessage implements DirectMessage {
|
||||||
private final NodeAddress senderNodeAddress;
|
private final NodeAddress senderNodeAddress;
|
||||||
private final PubKeyRing pubKeyRing;
|
private final PubKeyRing pubKeyRing;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final byte[] paymentAccountKey;
|
private final byte[] sellerPaymentAccountKey;
|
||||||
@Nullable
|
@Nullable
|
||||||
private final String updatedMultisigHex;
|
private final String updatedMultisigHex;
|
||||||
|
|
||||||
public PaymentAccountKeyResponse(String tradeId,
|
public DepositsConfirmedMessage(String tradeId,
|
||||||
NodeAddress senderNodeAddress,
|
NodeAddress senderNodeAddress,
|
||||||
PubKeyRing pubKeyRing,
|
PubKeyRing pubKeyRing,
|
||||||
String uid,
|
String uid,
|
||||||
String messageVersion,
|
@Nullable byte[] sellerPaymentAccountKey,
|
||||||
@Nullable byte[] paymentAccountKey,
|
|
||||||
@Nullable String updatedMultisigHex) {
|
@Nullable String updatedMultisigHex) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(Version.getP2PMessageVersion(), tradeId, uid);
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
this.senderNodeAddress = senderNodeAddress;
|
||||||
this.pubKeyRing = pubKeyRing;
|
this.pubKeyRing = pubKeyRing;
|
||||||
this.paymentAccountKey = paymentAccountKey;
|
this.sellerPaymentAccountKey = sellerPaymentAccountKey;
|
||||||
this.updatedMultisigHex = updatedMultisigHex;
|
this.updatedMultisigHex = updatedMultisigHex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,34 +61,34 @@ public final class PaymentAccountKeyResponse extends TradeMailboxMessage impleme
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
||||||
protobuf.PaymentAccountKeyResponse.Builder builder = protobuf.PaymentAccountKeyResponse.newBuilder()
|
protobuf.DepositsConfirmedMessage.Builder builder = protobuf.DepositsConfirmedMessage.newBuilder()
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
||||||
.setUid(uid);
|
.setUid(uid);
|
||||||
Optional.ofNullable(paymentAccountKey).ifPresent(e -> builder.setPaymentAccountKey(ByteString.copyFrom(e)));
|
Optional.ofNullable(sellerPaymentAccountKey).ifPresent(e -> builder.setSellerPaymentAccountKey(ByteString.copyFrom(e)));
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||||
return getNetworkEnvelopeBuilder().setPaymentAccountKeyResponse(builder).build();
|
return getNetworkEnvelopeBuilder().setDepositsConfirmedMessage(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PaymentAccountKeyResponse fromProto(protobuf.PaymentAccountKeyResponse proto,
|
public static DepositsConfirmedMessage fromProto(protobuf.DepositsConfirmedMessage proto,
|
||||||
CoreProtoResolver coreProtoResolver,
|
CoreProtoResolver coreProtoResolver,
|
||||||
String messageVersion) {
|
String messageVersion) {
|
||||||
return new PaymentAccountKeyResponse(proto.getTradeId(),
|
return new DepositsConfirmedMessage(proto.getTradeId(),
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
messageVersion,
|
ProtoUtil.byteArrayOrNullFromProto(proto.getSellerPaymentAccountKey()),
|
||||||
ProtoUtil.byteArrayOrNullFromProto(proto.getPaymentAccountKey()),
|
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "PaymentAccountKeyResponse {" +
|
return "DepositsConfirmedMessage {" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
",\n pubKeyRing=" + pubKeyRing +
|
||||||
",\n paymentAccountKey=" + paymentAccountKey +
|
",\n sellerPaymentAccountKey=" + sellerPaymentAccountKey +
|
||||||
|
",\n updatedMultisigHex=" + (updatedMultisigHex == null ? null : updatedMultisigHex.substring(0, Math.max(updatedMultisigHex.length(), 1000))) +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.messages;
|
|
||||||
|
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
|
||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Value
|
|
||||||
public final class PaymentAccountKeyRequest extends TradeMessage implements DirectMessage {
|
|
||||||
private final NodeAddress senderNodeAddress;
|
|
||||||
private final PubKeyRing pubKeyRing;
|
|
||||||
|
|
||||||
public PaymentAccountKeyRequest(String tradeId,
|
|
||||||
NodeAddress senderNodeAddress,
|
|
||||||
PubKeyRing pubKeyRing,
|
|
||||||
String uid,
|
|
||||||
String messageVersion) {
|
|
||||||
super(messageVersion, tradeId, uid);
|
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
|
||||||
this.pubKeyRing = pubKeyRing;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
|
||||||
protobuf.PaymentAccountKeyRequest.Builder builder = protobuf.PaymentAccountKeyRequest.newBuilder()
|
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
|
||||||
.setUid(uid);
|
|
||||||
return getNetworkEnvelopeBuilder().setPaymentAccountKeyRequest(builder).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PaymentAccountKeyRequest fromProto(protobuf.PaymentAccountKeyRequest proto,
|
|
||||||
CoreProtoResolver coreProtoResolver,
|
|
||||||
String messageVersion) {
|
|
||||||
return new PaymentAccountKeyRequest(proto.getTradeId(),
|
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
|
||||||
proto.getUid(),
|
|
||||||
messageVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PaymentAccountKeyRequest {" +
|
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
|
||||||
"\n} " + super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -22,6 +22,7 @@ import bisq.core.account.sign.SignedWitness;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
import bisq.common.app.Version;
|
||||||
|
import bisq.common.proto.ProtoUtil;
|
||||||
import bisq.common.proto.network.NetworkEnvelope;
|
import bisq.common.proto.network.NetworkEnvelope;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
@ -38,7 +39,12 @@ import javax.annotation.Nullable;
|
||||||
@Value
|
@Value
|
||||||
public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
private final NodeAddress senderNodeAddress;
|
private final NodeAddress senderNodeAddress;
|
||||||
private final String payoutTxHex;
|
@Nullable
|
||||||
|
private final String unsignedPayoutTxHex;
|
||||||
|
@Nullable
|
||||||
|
private final String signedPayoutTxHex;
|
||||||
|
private final String updatedMultisigHex;
|
||||||
|
private final boolean sawArrivedPaymentReceivedMsg;
|
||||||
|
|
||||||
// Added in v1.4.0
|
// Added in v1.4.0
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -47,13 +53,19 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
public PaymentReceivedMessage(String tradeId,
|
public PaymentReceivedMessage(String tradeId,
|
||||||
NodeAddress senderNodeAddress,
|
NodeAddress senderNodeAddress,
|
||||||
@Nullable SignedWitness signedWitness,
|
@Nullable SignedWitness signedWitness,
|
||||||
String signedPayoutTxHex) {
|
String unsignedPayoutTxHex,
|
||||||
|
String signedPayoutTxHex,
|
||||||
|
String updatedMultisigHex,
|
||||||
|
boolean sawArrivedPaymentReceivedMsg) {
|
||||||
this(tradeId,
|
this(tradeId,
|
||||||
senderNodeAddress,
|
senderNodeAddress,
|
||||||
signedWitness,
|
signedWitness,
|
||||||
UUID.randomUUID().toString(),
|
UUID.randomUUID().toString(),
|
||||||
Version.getP2PMessageVersion(),
|
Version.getP2PMessageVersion(),
|
||||||
signedPayoutTxHex);
|
unsignedPayoutTxHex,
|
||||||
|
signedPayoutTxHex,
|
||||||
|
updatedMultisigHex,
|
||||||
|
sawArrivedPaymentReceivedMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -66,11 +78,17 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
@Nullable SignedWitness signedWitness,
|
@Nullable SignedWitness signedWitness,
|
||||||
String uid,
|
String uid,
|
||||||
String messageVersion,
|
String messageVersion,
|
||||||
String signedPayoutTxHex) {
|
String unsignedPayoutTxHex,
|
||||||
|
String signedPayoutTxHex,
|
||||||
|
String updatedMultisigHex,
|
||||||
|
boolean sawArrivedPaymentReceivedMsg) {
|
||||||
super(messageVersion, tradeId, uid);
|
super(messageVersion, tradeId, uid);
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
this.senderNodeAddress = senderNodeAddress;
|
||||||
this.signedWitness = signedWitness;
|
this.signedWitness = signedWitness;
|
||||||
this.payoutTxHex = signedPayoutTxHex;
|
this.unsignedPayoutTxHex = unsignedPayoutTxHex;
|
||||||
|
this.signedPayoutTxHex = signedPayoutTxHex;
|
||||||
|
this.updatedMultisigHex = updatedMultisigHex;
|
||||||
|
this.sawArrivedPaymentReceivedMsg = sawArrivedPaymentReceivedMsg;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,8 +97,11 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
.setTradeId(tradeId)
|
.setTradeId(tradeId)
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
||||||
.setUid(uid)
|
.setUid(uid)
|
||||||
.setPayoutTxHex(payoutTxHex);
|
.setSawArrivedPaymentReceivedMsg(sawArrivedPaymentReceivedMsg);
|
||||||
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
|
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
|
||||||
|
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
||||||
|
Optional.ofNullable(unsignedPayoutTxHex).ifPresent(e -> builder.setUnsignedPayoutTxHex(unsignedPayoutTxHex));
|
||||||
|
Optional.ofNullable(signedPayoutTxHex).ifPresent(e -> builder.setSignedPayoutTxHex(signedPayoutTxHex));
|
||||||
return getNetworkEnvelopeBuilder().setPaymentReceivedMessage(builder).build();
|
return getNetworkEnvelopeBuilder().setPaymentReceivedMessage(builder).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,7 +117,10 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
signedWitness,
|
signedWitness,
|
||||||
proto.getUid(),
|
proto.getUid(),
|
||||||
messageVersion,
|
messageVersion,
|
||||||
proto.getPayoutTxHex());
|
ProtoUtil.stringOrNullFromProto(proto.getUnsignedPayoutTxHex()),
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getSignedPayoutTxHex()),
|
||||||
|
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()),
|
||||||
|
proto.getSawArrivedPaymentReceivedMsg());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -104,7 +128,10 @@ public final class PaymentReceivedMessage extends TradeMailboxMessage {
|
||||||
return "SellerReceivedPaymentMessage{" +
|
return "SellerReceivedPaymentMessage{" +
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
"\n senderNodeAddress=" + senderNodeAddress +
|
||||||
",\n signedWitness=" + signedWitness +
|
",\n signedWitness=" + signedWitness +
|
||||||
",\n payoutTxHex=" + payoutTxHex +
|
",\n unsignedPayoutTxHex=" + unsignedPayoutTxHex +
|
||||||
|
",\n signedPayoutTxHex=" + signedPayoutTxHex +
|
||||||
|
",\n updatedMultisigHex=" + (updatedMultisigHex == null ? null : updatedMultisigHex.substring(0, Math.max(updatedMultisigHex.length(), 1000))) +
|
||||||
|
",\n sawArrivedPaymentReceivedMsg=" + sawArrivedPaymentReceivedMsg +
|
||||||
"\n} " + super.toString();
|
"\n} " + super.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,8 +52,8 @@ public final class PaymentSentMessage extends TradeMailboxMessage {
|
||||||
@Nullable String counterCurrencyTxId,
|
@Nullable String counterCurrencyTxId,
|
||||||
@Nullable String counterCurrencyExtraData,
|
@Nullable String counterCurrencyExtraData,
|
||||||
String uid,
|
String uid,
|
||||||
String signedPayoutTxHex,
|
@Nullable String signedPayoutTxHex,
|
||||||
String updatedMultisigHex,
|
@Nullable String updatedMultisigHex,
|
||||||
@Nullable byte[] paymentAccountKey) {
|
@Nullable byte[] paymentAccountKey) {
|
||||||
this(tradeId,
|
this(tradeId,
|
||||||
senderNodeAddress,
|
senderNodeAddress,
|
||||||
|
|
|
@ -1,118 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.messages;
|
|
||||||
|
|
||||||
import bisq.core.account.sign.SignedWitness;
|
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
|
||||||
import bisq.common.proto.network.NetworkEnvelope;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Value;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Value
|
|
||||||
public final class PayoutTxPublishedMessage extends TradeMailboxMessage {
|
|
||||||
private final NodeAddress senderNodeAddress;
|
|
||||||
private final boolean isMaker;
|
|
||||||
private final String signedPayoutTxHex;
|
|
||||||
|
|
||||||
// Added in v1.4.0
|
|
||||||
@Nullable
|
|
||||||
private final SignedWitness signedWitness;
|
|
||||||
|
|
||||||
public PayoutTxPublishedMessage(String tradeId,
|
|
||||||
NodeAddress senderNodeAddress,
|
|
||||||
boolean isMaker,
|
|
||||||
@Nullable SignedWitness signedWitness,
|
|
||||||
String signedPayoutTxHex) {
|
|
||||||
this(tradeId,
|
|
||||||
senderNodeAddress,
|
|
||||||
isMaker,
|
|
||||||
signedWitness,
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
Version.getP2PMessageVersion(),
|
|
||||||
signedPayoutTxHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
private PayoutTxPublishedMessage(String tradeId,
|
|
||||||
NodeAddress senderNodeAddress,
|
|
||||||
boolean isMaker,
|
|
||||||
@Nullable SignedWitness signedWitness,
|
|
||||||
String uid,
|
|
||||||
String messageVersion,
|
|
||||||
String signedPayoutTxHex) {
|
|
||||||
super(messageVersion, tradeId, uid);
|
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
|
||||||
this.isMaker = isMaker;
|
|
||||||
this.signedWitness = signedWitness;
|
|
||||||
this.signedPayoutTxHex = signedPayoutTxHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
|
||||||
protobuf.PayoutTxPublishedMessage.Builder builder = protobuf.PayoutTxPublishedMessage.newBuilder()
|
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
|
||||||
.setIsMaker(isMaker)
|
|
||||||
.setUid(uid)
|
|
||||||
.setSignedPayoutTxHex(signedPayoutTxHex);
|
|
||||||
Optional.ofNullable(signedWitness).ifPresent(signedWitness -> builder.setSignedWitness(signedWitness.toProtoSignedWitness()));
|
|
||||||
return getNetworkEnvelopeBuilder().setPayoutTxPublishedMessage(builder).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkEnvelope fromProto(protobuf.PayoutTxPublishedMessage proto, String messageVersion) {
|
|
||||||
// There is no method to check for a nullable non-primitive data type object but we know that all fields
|
|
||||||
// are empty/null, so we check for the signature to see if we got a valid signedWitness.
|
|
||||||
protobuf.SignedWitness protoSignedWitness = proto.getSignedWitness();
|
|
||||||
SignedWitness signedWitness = !protoSignedWitness.getSignature().isEmpty() ?
|
|
||||||
SignedWitness.fromProto(protoSignedWitness) :
|
|
||||||
null;
|
|
||||||
return new PayoutTxPublishedMessage(proto.getTradeId(),
|
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
|
||||||
proto.getIsMaker(),
|
|
||||||
signedWitness,
|
|
||||||
proto.getUid(),
|
|
||||||
messageVersion,
|
|
||||||
proto.getSignedPayoutTxHex());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "PayoutTxPublishedMessage{" +
|
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
|
||||||
",\n isMaker=" + isMaker +
|
|
||||||
",\n signedWitness=" + signedWitness +
|
|
||||||
",\n signedPayoutTxHex=" + signedPayoutTxHex +
|
|
||||||
"\n} " + super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.messages;
|
|
||||||
|
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
|
||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.proto.ProtoUtil;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Value
|
|
||||||
public final class UpdateMultisigRequest extends TradeMessage implements DirectMessage {
|
|
||||||
private final NodeAddress senderNodeAddress;
|
|
||||||
private final PubKeyRing pubKeyRing;
|
|
||||||
private final long currentDate;
|
|
||||||
@Nullable
|
|
||||||
private final String updatedMultisigHex;
|
|
||||||
|
|
||||||
public UpdateMultisigRequest(String tradeId,
|
|
||||||
NodeAddress senderNodeAddress,
|
|
||||||
PubKeyRing pubKeyRing,
|
|
||||||
String uid,
|
|
||||||
String messageVersion,
|
|
||||||
long currentDate,
|
|
||||||
String updatedMultisigHex) {
|
|
||||||
super(messageVersion, tradeId, uid);
|
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
|
||||||
this.pubKeyRing = pubKeyRing;
|
|
||||||
this.currentDate = currentDate;
|
|
||||||
this.updatedMultisigHex = updatedMultisigHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
|
||||||
protobuf.UpdateMultisigRequest.Builder builder = protobuf.UpdateMultisigRequest.newBuilder()
|
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
|
||||||
.setUid(uid);
|
|
||||||
|
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
|
||||||
|
|
||||||
builder.setCurrentDate(currentDate);
|
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setUpdateMultisigRequest(builder).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UpdateMultisigRequest fromProto(protobuf.UpdateMultisigRequest proto,
|
|
||||||
CoreProtoResolver coreProtoResolver,
|
|
||||||
String messageVersion) {
|
|
||||||
return new UpdateMultisigRequest(proto.getTradeId(),
|
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
|
||||||
proto.getUid(),
|
|
||||||
messageVersion,
|
|
||||||
proto.getCurrentDate(),
|
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "UpdateMultisigRequest {" +
|
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
|
||||||
",\n currentDate=" + currentDate +
|
|
||||||
",\n updatedMultisigHex='" + updatedMultisigHex +
|
|
||||||
"\n} " + super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.messages;
|
|
||||||
|
|
||||||
import bisq.core.proto.CoreProtoResolver;
|
|
||||||
|
|
||||||
import bisq.network.p2p.DirectMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.proto.ProtoUtil;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.Value;
|
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Value
|
|
||||||
public final class UpdateMultisigResponse extends TradeMessage implements DirectMessage {
|
|
||||||
private final NodeAddress senderNodeAddress;
|
|
||||||
private final PubKeyRing pubKeyRing;
|
|
||||||
private final long currentDate;
|
|
||||||
@Nullable
|
|
||||||
private final String updatedMultisigHex;
|
|
||||||
|
|
||||||
public UpdateMultisigResponse(String tradeId,
|
|
||||||
NodeAddress senderNodeAddress,
|
|
||||||
PubKeyRing pubKeyRing,
|
|
||||||
String uid,
|
|
||||||
String messageVersion,
|
|
||||||
long currentDate,
|
|
||||||
String updatedMultisigHex) {
|
|
||||||
super(messageVersion, tradeId, uid);
|
|
||||||
this.senderNodeAddress = senderNodeAddress;
|
|
||||||
this.pubKeyRing = pubKeyRing;
|
|
||||||
this.currentDate = currentDate;
|
|
||||||
this.updatedMultisigHex = updatedMultisigHex;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// PROTO BUFFER
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public protobuf.NetworkEnvelope toProtoNetworkEnvelope() {
|
|
||||||
protobuf.UpdateMultisigResponse.Builder builder = protobuf.UpdateMultisigResponse.newBuilder()
|
|
||||||
.setTradeId(tradeId)
|
|
||||||
.setSenderNodeAddress(senderNodeAddress.toProtoMessage())
|
|
||||||
.setPubKeyRing(pubKeyRing.toProtoMessage())
|
|
||||||
.setUid(uid);
|
|
||||||
|
|
||||||
Optional.ofNullable(updatedMultisigHex).ifPresent(e -> builder.setUpdatedMultisigHex(updatedMultisigHex));
|
|
||||||
|
|
||||||
builder.setCurrentDate(currentDate);
|
|
||||||
|
|
||||||
return getNetworkEnvelopeBuilder().setUpdateMultisigResponse(builder).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UpdateMultisigResponse fromProto(protobuf.UpdateMultisigResponse proto,
|
|
||||||
CoreProtoResolver coreProtoResolver,
|
|
||||||
String messageVersion) {
|
|
||||||
return new UpdateMultisigResponse(proto.getTradeId(),
|
|
||||||
NodeAddress.fromProto(proto.getSenderNodeAddress()),
|
|
||||||
PubKeyRing.fromProto(proto.getPubKeyRing()),
|
|
||||||
proto.getUid(),
|
|
||||||
messageVersion,
|
|
||||||
proto.getCurrentDate(),
|
|
||||||
ProtoUtil.stringOrNullFromProto(proto.getUpdatedMultisigHex()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "UpdateMultisigResponse {" +
|
|
||||||
"\n senderNodeAddress=" + senderNodeAddress +
|
|
||||||
",\n pubKeyRing=" + pubKeyRing +
|
|
||||||
",\n currentDate=" + currentDate +
|
|
||||||
",\n updatedMultisigHex='" + updatedMultisigHex +
|
|
||||||
"\n} " + super.toString();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,18 +6,16 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DepositRequest;
|
import bisq.core.trade.messages.DepositRequest;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyRequest;
|
|
||||||
import bisq.core.trade.messages.SignContractResponse;
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.FluentProtocol.Condition;
|
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||||
import bisq.core.trade.protocol.tasks.ArbitratorProcessDepositRequest;
|
import bisq.core.trade.protocol.tasks.ArbitratorProcessDepositRequest;
|
||||||
import bisq.core.trade.protocol.tasks.ArbitratorProcessPaymentAccountKeyRequest;
|
|
||||||
import bisq.core.trade.protocol.tasks.ArbitratorProcessReserveTx;
|
import bisq.core.trade.protocol.tasks.ArbitratorProcessReserveTx;
|
||||||
import bisq.core.trade.protocol.tasks.ArbitratorProcessPayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.ArbitratorSendInitTradeOrMultisigRequests;
|
import bisq.core.trade.protocol.tasks.ArbitratorSendInitTradeOrMultisigRequests;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
||||||
|
import bisq.core.trade.protocol.tasks.SendDepositsConfirmedMessageToBuyer;
|
||||||
|
import bisq.core.trade.protocol.tasks.SendDepositsConfirmedMessageToSeller;
|
||||||
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -32,17 +30,11 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
||||||
@Override
|
@Override
|
||||||
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
||||||
super.onTradeMessage(message, peer);
|
super.onTradeMessage(message, peer);
|
||||||
if (message instanceof PayoutTxPublishedMessage) {
|
|
||||||
handle((PayoutTxPublishedMessage) message, peer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMailboxMessage(TradeMessage message, NodeAddress peer) {
|
public void onMailboxMessage(TradeMessage message, NodeAddress peer) {
|
||||||
super.onMailboxMessage(message, peer);
|
super.onMailboxMessage(message, peer);
|
||||||
if (message instanceof PayoutTxPublishedMessage) {
|
|
||||||
handle((PayoutTxPublishedMessage) message, peer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -118,57 +110,10 @@ public class ArbitratorProtocol extends DisputeProtocol {
|
||||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
|
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
|
||||||
log.warn("Arbitrator ignoring DepositResponse for trade " + response.getTradeId());
|
log.warn("Arbitrator ignoring DepositResponse for trade " + response.getTradeId());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handlePaymentAccountKeyRequest(PaymentAccountKeyRequest request, NodeAddress sender) {
|
@SuppressWarnings("unchecked")
|
||||||
System.out.println("ArbitratorProtocol.handlePaymentAccountKeyRequest() " + trade.getId());
|
@Override
|
||||||
new Thread(() -> {
|
public Class<? extends TradeTask>[] getDepsitsConfirmedTasks() {
|
||||||
synchronized (trade) {
|
return new Class[] { SendDepositsConfirmedMessageToBuyer.class, SendDepositsConfirmedMessageToSeller.class };
|
||||||
latchTrade();
|
|
||||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
|
||||||
processModel.setTradeMessage(request);
|
|
||||||
expect(new Condition(trade)
|
|
||||||
.with(request)
|
|
||||||
.from(sender))
|
|
||||||
.setup(tasks(
|
|
||||||
ArbitratorProcessPaymentAccountKeyRequest.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
stopTimeout();
|
|
||||||
handleTaskRunnerSuccess(sender, request);
|
|
||||||
},
|
|
||||||
errorMessage -> {
|
|
||||||
handleTaskRunnerFault(sender, request, errorMessage);
|
|
||||||
}))
|
|
||||||
.withTimeout(TRADE_TIMEOUT))
|
|
||||||
.executeTasks(true);
|
|
||||||
awaitTradeLatch();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void handle(PayoutTxPublishedMessage request, NodeAddress peer) {
|
|
||||||
System.out.println("ArbitratorProtocol.handle(PayoutTxPublishedMessage)");
|
|
||||||
new Thread(() -> {
|
|
||||||
synchronized (trade) {
|
|
||||||
if (trade.isCompleted()) return; // ignore subsequent requests
|
|
||||||
latchTrade();
|
|
||||||
Validator.checkTradeId(processModel.getOfferId(), request);
|
|
||||||
processModel.setTradeMessage(request);
|
|
||||||
expect(anyPhase(Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED, Trade.Phase.DEPOSITS_UNLOCKED)
|
|
||||||
.with(request)
|
|
||||||
.from(peer))
|
|
||||||
.setup(tasks(
|
|
||||||
ArbitratorProcessPayoutTxPublishedMessage.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
handleTaskRunnerSuccess(peer, request);
|
|
||||||
},
|
|
||||||
errorMessage -> {
|
|
||||||
handleTaskRunnerFault(peer, request, errorMessage);
|
|
||||||
})))
|
|
||||||
.executeTasks(true);
|
|
||||||
awaitTradeLatch();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
import bisq.core.trade.messages.SignContractRequest;
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
import bisq.core.trade.messages.SignContractResponse;
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
|
@ -31,7 +31,7 @@ import bisq.core.trade.protocol.tasks.ProcessInitTradeRequest;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
import bisq.common.taskrunner.Task;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -102,7 +102,7 @@ public class BuyerAsMakerProtocol extends BuyerProtocol implements MakerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(PaymentAccountKeyResponse request, NodeAddress sender) {
|
public void handle(DepositsConfirmedMessage request, NodeAddress sender) {
|
||||||
super.handle(request, sender);
|
super.handle(request, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.handlers.TradeResultHandler;
|
import bisq.core.trade.handlers.TradeResultHandler;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
import bisq.core.trade.messages.SignContractRequest;
|
import bisq.core.trade.messages.SignContractRequest;
|
||||||
import bisq.core.trade.messages.SignContractResponse;
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
|
@ -113,7 +113,7 @@ public class BuyerAsTakerProtocol extends BuyerProtocol implements TakerProtocol
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(PaymentAccountKeyResponse request, NodeAddress sender) {
|
public void handle(DepositsConfirmedMessage request, NodeAddress sender) {
|
||||||
super.handle(request, sender);
|
super.handle(request, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,35 +17,23 @@
|
||||||
|
|
||||||
package bisq.core.trade.protocol;
|
package bisq.core.trade.protocol;
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
import bisq.core.trade.BuyerTrade;
|
import bisq.core.trade.BuyerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
|
||||||
import bisq.core.trade.messages.SignContractResponse;
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.FluentProtocol.Condition;
|
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentAccountKeyResponse;
|
|
||||||
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.BuyerSendPaymentAccountKeyRequestToArbitrator;
|
|
||||||
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerSendPayoutTxPublishedMessage;
|
import bisq.core.trade.protocol.tasks.SendDepositsConfirmedMessageToArbitrator;
|
||||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
|
|
||||||
import bisq.core.util.Validator;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class BuyerProtocol extends DisputeProtocol {
|
public class BuyerProtocol extends DisputeProtocol {
|
||||||
|
|
||||||
private boolean listeningToSendPaymentAccountKey;
|
|
||||||
private boolean paymentAccountPayloadKeyRequestSent;
|
|
||||||
enum BuyerEvent implements FluentProtocol.Event {
|
enum BuyerEvent implements FluentProtocol.Event {
|
||||||
STARTUP,
|
STARTUP,
|
||||||
DEPOSIT_TXS_CONFIRMED,
|
DEPOSIT_TXS_CONFIRMED,
|
||||||
|
@ -66,23 +54,8 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
||||||
|
|
||||||
// TODO: run with trade lock and latch, otherwise getting invalid transition warnings on startup after offline trades
|
// TODO: run with trade lock and latch, otherwise getting invalid transition warnings on startup after offline trades
|
||||||
|
|
||||||
// request key to decrypt seller's payment account payload after first confirmation
|
|
||||||
sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.STARTUP, false);
|
|
||||||
|
|
||||||
// listen for deposit txs
|
|
||||||
given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
|
|
||||||
.with(BuyerEvent.STARTUP))
|
|
||||||
.setup(tasks(SetupDepositTxsListener.class))
|
|
||||||
.executeTasks();
|
|
||||||
|
|
||||||
// listen for payout tx
|
|
||||||
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
|
|
||||||
.with(BuyerEvent.STARTUP))
|
|
||||||
.setup(tasks(SetupPayoutTxListener.class))
|
|
||||||
.executeTasks();
|
|
||||||
|
|
||||||
// send payment sent message
|
// send payment sent message
|
||||||
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED) // TODO: remove payment received phase?
|
given(anyPhase(Trade.Phase.PAYMENT_SENT)
|
||||||
.anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG)
|
.anyState(Trade.State.BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG, Trade.State.BUYER_SEND_FAILED_PAYMENT_SENT_MSG)
|
||||||
.with(BuyerEvent.STARTUP))
|
.with(BuyerEvent.STARTUP))
|
||||||
.setup(tasks(BuyerSendPaymentSentMessage.class))
|
.setup(tasks(BuyerSendPaymentSentMessage.class))
|
||||||
|
@ -92,49 +65,16 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
||||||
@Override
|
@Override
|
||||||
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
protected void onTradeMessage(TradeMessage message, NodeAddress peer) {
|
||||||
super.onTradeMessage(message, peer);
|
super.onTradeMessage(message, peer);
|
||||||
if (message instanceof PaymentReceivedMessage) {
|
|
||||||
handle((PaymentReceivedMessage) message, peer);
|
|
||||||
} if (message instanceof PaymentAccountKeyResponse) {
|
|
||||||
handle((PaymentAccountKeyResponse) message, peer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMailboxMessage(TradeMessage message, NodeAddress peer) {
|
public void onMailboxMessage(TradeMessage message, NodeAddress peer) {
|
||||||
super.onMailboxMessage(message, peer);
|
super.onMailboxMessage(message, peer);
|
||||||
if (message instanceof PaymentReceivedMessage) {
|
|
||||||
handle((PaymentReceivedMessage) message, peer);
|
|
||||||
} else if (message instanceof PaymentAccountKeyResponse) {
|
|
||||||
handle((PaymentAccountKeyResponse) message, peer);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) {
|
public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) {
|
||||||
super.handleSignContractResponse(response, sender);
|
super.handleSignContractResponse(response, sender);
|
||||||
sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent.DEPOSIT_TXS_CONFIRMED, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void handle(PaymentAccountKeyResponse response, NodeAddress sender) {
|
|
||||||
System.out.println(getClass().getCanonicalName() + ".handlePaymentAccountKeyResponse()");
|
|
||||||
new Thread(() -> {
|
|
||||||
synchronized (trade) {
|
|
||||||
latchTrade();
|
|
||||||
expect(new Condition(trade)
|
|
||||||
.with(response)
|
|
||||||
.from(sender))
|
|
||||||
.setup(tasks(BuyerProcessPaymentAccountKeyResponse.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
handleTaskRunnerSuccess(sender, response);
|
|
||||||
},
|
|
||||||
errorMessage -> {
|
|
||||||
handleTaskRunnerFault(sender, response, errorMessage);
|
|
||||||
})))
|
|
||||||
.executeTasks();
|
|
||||||
awaitTradeLatch();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -177,84 +117,9 @@ public abstract class BuyerProtocol extends DisputeProtocol {
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
@SuppressWarnings("unchecked")
|
||||||
// Incoming message Payout tx
|
@Override
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
public Class<? extends TradeTask>[] getDepsitsConfirmedTasks() {
|
||||||
|
return new Class[] { SendDepositsConfirmedMessageToArbitrator.class };
|
||||||
protected void handle(PaymentReceivedMessage message, NodeAddress peer) {
|
|
||||||
System.out.println("BuyerProtocol.handle(PaymentReceivedMessage)");
|
|
||||||
new Thread(() -> {
|
|
||||||
synchronized (trade) {
|
|
||||||
latchTrade();
|
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
|
||||||
processModel.setTradeMessage(message);
|
|
||||||
expect(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
|
|
||||||
.with(message)
|
|
||||||
.from(peer))
|
|
||||||
.setup(tasks(
|
|
||||||
BuyerProcessPaymentReceivedMessage.class,
|
|
||||||
BuyerSendPayoutTxPublishedMessage.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
handleTaskRunnerSuccess(peer, message);
|
|
||||||
},
|
|
||||||
errorMessage -> {
|
|
||||||
handleTaskRunnerFault(peer, message, errorMessage);
|
|
||||||
})))
|
|
||||||
.executeTasks(true);
|
|
||||||
awaitTradeLatch();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPaymentAccountKeyRequestIfWhenNeeded(BuyerEvent event, boolean waitForSellerOnConfirm) {
|
|
||||||
|
|
||||||
// skip if payment account payload already decrypted or not enough progress
|
|
||||||
if (trade.getSeller().getPaymentAccountPayload() != null) return;
|
|
||||||
if (trade.getPhase().ordinal() < Trade.Phase.DEPOSIT_REQUESTED.ordinal()) return;
|
|
||||||
|
|
||||||
// if confirmed and waiting for seller, recheck later
|
|
||||||
if (trade.getState() == Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN && waitForSellerOnConfirm) {
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
sendPaymentAccountKeyRequestIfWhenNeeded(event, false);
|
|
||||||
}, TRADE_TIMEOUT);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// else if confirmed send request and return
|
|
||||||
else if (trade.getState().ordinal() >= Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN.ordinal()) {
|
|
||||||
sendPaymentAccountKeyRequest(event);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// register for state changes once
|
|
||||||
if (!listeningToSendPaymentAccountKey) {
|
|
||||||
listeningToSendPaymentAccountKey = true;
|
|
||||||
EasyBind.subscribe(trade.stateProperty(), state -> {
|
|
||||||
sendPaymentAccountKeyRequestIfWhenNeeded(event, waitForSellerOnConfirm);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendPaymentAccountKeyRequest(BuyerEvent event) {
|
|
||||||
new Thread(() -> {
|
|
||||||
synchronized (trade) {
|
|
||||||
if (paymentAccountPayloadKeyRequestSent) return;
|
|
||||||
if (trade.getSeller().getPaymentAccountPayload() != null) return; // skip if initialized
|
|
||||||
latchTrade();
|
|
||||||
expect(new Condition(trade))
|
|
||||||
.setup(tasks(BuyerSendPaymentAccountKeyRequestToArbitrator.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
handleTaskRunnerSuccess(event);
|
|
||||||
},
|
|
||||||
(errorMessage) -> {
|
|
||||||
handleTaskRunnerFault(event, errorMessage);
|
|
||||||
})))
|
|
||||||
.executeTasks(true);
|
|
||||||
awaitTradeLatch();
|
|
||||||
paymentAccountPayloadKeyRequestSent = true;
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,25 +22,20 @@ import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentSentMessage;
|
import bisq.core.trade.messages.PaymentSentMessage;
|
||||||
import bisq.core.trade.messages.SignContractResponse;
|
import bisq.core.trade.messages.SignContractResponse;
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.protocol.BuyerProtocol.BuyerEvent;
|
|
||||||
import bisq.core.trade.protocol.FluentProtocol.Condition;
|
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||||
import bisq.core.trade.protocol.tasks.SellerMaybeSendPayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
|
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToArbitrator;
|
||||||
import bisq.core.trade.protocol.tasks.SellerSendPaymentAccountPayloadKey;
|
import bisq.core.trade.protocol.tasks.SendDepositsConfirmedMessageToBuyer;
|
||||||
import bisq.core.trade.protocol.tasks.SetupDepositTxsListener;
|
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
||||||
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
import bisq.network.p2p.NodeAddress;
|
import bisq.network.p2p.NodeAddress;
|
||||||
import bisq.common.handlers.ErrorMessageHandler;
|
import bisq.common.handlers.ErrorMessageHandler;
|
||||||
import bisq.common.handlers.ResultHandler;
|
import bisq.common.handlers.ResultHandler;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class SellerProtocol extends DisputeProtocol {
|
public class SellerProtocol extends DisputeProtocol {
|
||||||
enum SellerEvent implements FluentProtocol.Event {
|
enum SellerEvent implements FluentProtocol.Event {
|
||||||
STARTUP,
|
STARTUP,
|
||||||
DEPOSIT_TXS_CONFIRMED,
|
DEPOSIT_TXS_CONFIRMED,
|
||||||
|
@ -54,25 +49,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||||
@Override
|
@Override
|
||||||
protected void onInitialized() {
|
protected void onInitialized() {
|
||||||
super.onInitialized();
|
super.onInitialized();
|
||||||
|
|
||||||
// TODO: run with trade lock and latch, otherwise getting invalid transition warnings on startup after offline trades
|
|
||||||
|
|
||||||
// send payment account payload key when trade state is confirmed
|
|
||||||
if (trade.getPhase() == Trade.Phase.DEPOSIT_REQUESTED || trade.getPhase() == Trade.Phase.DEPOSITS_PUBLISHED) {
|
|
||||||
sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.STARTUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
// listen for deposit txs
|
|
||||||
given(anyPhase(Trade.Phase.DEPOSIT_REQUESTED, Trade.Phase.DEPOSITS_PUBLISHED, Trade.Phase.DEPOSITS_CONFIRMED)
|
|
||||||
.with(SellerEvent.STARTUP))
|
|
||||||
.setup(tasks(SetupDepositTxsListener.class))
|
|
||||||
.executeTasks();
|
|
||||||
|
|
||||||
// listen for payout tx
|
|
||||||
given(anyPhase(Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED)
|
|
||||||
.with(BuyerEvent.STARTUP))
|
|
||||||
.setup(tasks(SetupPayoutTxListener.class))
|
|
||||||
.executeTasks();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -94,7 +70,6 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) {
|
public void handleSignContractResponse(SignContractResponse response, NodeAddress sender) {
|
||||||
sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent.DEPOSIT_TXS_CONFIRMED);
|
|
||||||
super.handleSignContractResponse(response, sender);
|
super.handleSignContractResponse(response, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,8 +138,8 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||||
.setup(tasks(
|
.setup(tasks(
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
SellerPreparePaymentReceivedMessage.class,
|
SellerPreparePaymentReceivedMessage.class,
|
||||||
SellerMaybeSendPayoutTxPublishedMessage.class,
|
SellerSendPaymentReceivedMessageToBuyer.class,
|
||||||
SellerSendPaymentReceivedMessage.class)
|
SellerSendPaymentReceivedMessageToArbitrator.class)
|
||||||
.using(new TradeTaskRunner(trade, () -> {
|
.using(new TradeTaskRunner(trade, () -> {
|
||||||
this.errorMessageHandler = null;
|
this.errorMessageHandler = null;
|
||||||
handleTaskRunnerSuccess(event);
|
handleTaskRunnerSuccess(event);
|
||||||
|
@ -183,26 +158,9 @@ public abstract class SellerProtocol extends DisputeProtocol {
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPaymentAccountPayloadKeyWhenConfirmed(SellerEvent event) {
|
@SuppressWarnings("unchecked")
|
||||||
EasyBind.subscribe(trade.stateProperty(), state -> {
|
@Override
|
||||||
if (state == Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN) {
|
public Class<? extends TradeTask>[] getDepsitsConfirmedTasks() {
|
||||||
new Thread(() -> {
|
return new Class[] { SendDepositsConfirmedMessageToBuyer.class };
|
||||||
synchronized (trade) {
|
|
||||||
latchTrade();
|
|
||||||
expect(new Condition(trade))
|
|
||||||
.setup(tasks(SellerSendPaymentAccountPayloadKey.class)
|
|
||||||
.using(new TradeTaskRunner(trade,
|
|
||||||
() -> {
|
|
||||||
handleTaskRunnerSuccess(event);
|
|
||||||
},
|
|
||||||
(errorMessage) -> {
|
|
||||||
handleTaskRunnerFault(event, errorMessage);
|
|
||||||
})))
|
|
||||||
.executeTasks(true);
|
|
||||||
awaitTradeLatch();
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,24 +18,30 @@
|
||||||
package bisq.core.trade.protocol;
|
package bisq.core.trade.protocol;
|
||||||
|
|
||||||
import bisq.core.offer.Offer;
|
import bisq.core.offer.Offer;
|
||||||
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
|
import bisq.core.trade.BuyerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeManager;
|
import bisq.core.trade.TradeManager;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.handlers.TradeResultHandler;
|
import bisq.core.trade.handlers.TradeResultHandler;
|
||||||
import bisq.core.trade.messages.PaymentSentMessage;
|
import bisq.core.trade.messages.PaymentSentMessage;
|
||||||
import bisq.core.trade.messages.DepositResponse;
|
import bisq.core.trade.messages.DepositResponse;
|
||||||
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import bisq.core.trade.messages.InitMultisigRequest;
|
import bisq.core.trade.messages.InitMultisigRequest;
|
||||||
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
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.messages.TradeMessage;
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
|
||||||
import bisq.core.trade.protocol.tasks.RemoveOffer;
|
import bisq.core.trade.protocol.tasks.RemoveOffer;
|
||||||
|
import bisq.core.trade.protocol.tasks.TradeTask;
|
||||||
|
import bisq.core.trade.protocol.FluentProtocol.Condition;
|
||||||
import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest;
|
import bisq.core.trade.protocol.tasks.MaybeSendSignContractRequest;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
import bisq.core.trade.protocol.tasks.ProcessDepositResponse;
|
||||||
|
import bisq.core.trade.protocol.tasks.ProcessDepositsConfirmedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
import bisq.core.trade.protocol.tasks.ProcessInitMultisigRequest;
|
||||||
|
import bisq.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
import bisq.core.trade.protocol.tasks.ProcessSignContractRequest;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
import bisq.core.trade.protocol.tasks.ProcessSignContractResponse;
|
||||||
import bisq.core.trade.protocol.tasks.ProcessUpdateMultisigRequest;
|
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
|
||||||
import bisq.network.p2p.AckMessage;
|
import bisq.network.p2p.AckMessage;
|
||||||
|
@ -47,7 +53,6 @@ import bisq.network.p2p.SendMailboxMessageListener;
|
||||||
import bisq.network.p2p.mailbox.MailboxMessage;
|
import bisq.network.p2p.mailbox.MailboxMessage;
|
||||||
import bisq.network.p2p.mailbox.MailboxMessageService;
|
import bisq.network.p2p.mailbox.MailboxMessageService;
|
||||||
import bisq.network.p2p.messaging.DecryptedMailboxListener;
|
import bisq.network.p2p.messaging.DecryptedMailboxListener;
|
||||||
|
|
||||||
import bisq.common.Timer;
|
import bisq.common.Timer;
|
||||||
import bisq.common.UserThread;
|
import bisq.common.UserThread;
|
||||||
import bisq.common.crypto.PubKeyRing;
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
@ -58,7 +63,6 @@ import bisq.common.taskrunner.Task;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.fxmisc.easybind.EasyBind;
|
import org.fxmisc.easybind.EasyBind;
|
||||||
|
@ -93,10 +97,20 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
|
|
||||||
protected void onTradeMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
protected void onTradeMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||||
log.info("Received {} as TradeMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid());
|
log.info("Received {} as TradeMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid());
|
||||||
|
if (message instanceof DepositsConfirmedMessage) {
|
||||||
|
handle((DepositsConfirmedMessage) message, peerNodeAddress);
|
||||||
|
} else if (message instanceof PaymentReceivedMessage) {
|
||||||
|
handle((PaymentReceivedMessage) message, peerNodeAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
protected void onMailboxMessage(TradeMessage message, NodeAddress peerNodeAddress) {
|
||||||
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid());
|
log.info("Received {} as MailboxMessage from {} with tradeId {} and uid {}", message.getClass().getSimpleName(), peerNodeAddress, message.getTradeId(), message.getUid());
|
||||||
|
if (message instanceof DepositsConfirmedMessage) {
|
||||||
|
handle((DepositsConfirmedMessage) message, peerNodeAddress);
|
||||||
|
} else if (message instanceof PaymentReceivedMessage) {
|
||||||
|
handle((PaymentReceivedMessage) message, peerNodeAddress);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,20 +124,22 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void onInitialized() {
|
protected void onInitialized() {
|
||||||
if (!trade.isWithdrawn()) {
|
if (!trade.isCompleted()) {
|
||||||
processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
processModel.getP2PService().addDecryptedDirectMessageListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle trade events
|
||||||
|
EasyBind.subscribe(trade.stateProperty(), state -> {
|
||||||
|
if (state == Trade.State.DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN) sendDepositsConfirmedMessage();
|
||||||
|
});
|
||||||
|
|
||||||
|
// initialize trade
|
||||||
|
trade.initialize(processModel.getProvider());
|
||||||
|
|
||||||
|
// process mailbox messages
|
||||||
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
MailboxMessageService mailboxMessageService = processModel.getP2PService().getMailboxMessageService();
|
||||||
// We delay a bit here as the trade gets updated from the wallet to update the trade
|
mailboxMessageService.addDecryptedMailboxListener(this);
|
||||||
// state (deposit confirmed) and that happens after our method is called.
|
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
||||||
// TODO To fix that in a better way we would need to change the order of some routines
|
|
||||||
// from the TradeManager, but as we are close to a release I dont want to risk a bigger
|
|
||||||
// change and leave that for a later PR
|
|
||||||
UserThread.runAfter(() -> {
|
|
||||||
mailboxMessageService.addDecryptedMailboxListener(this);
|
|
||||||
handleMailboxCollection(mailboxMessageService.getMyDecryptedMailboxMessages());
|
|
||||||
}, 100, TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onWithdrawCompleted() {
|
public void onWithdrawCompleted() {
|
||||||
|
@ -196,7 +212,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
TradeMessage tradeMessage = (TradeMessage) mailboxMessage;
|
TradeMessage tradeMessage = (TradeMessage) mailboxMessage;
|
||||||
// We only remove here if we have already completed the trade.
|
// We only remove here if we have already completed the trade.
|
||||||
// Otherwise removal is done after successfully applied the task runner.
|
// Otherwise removal is done after successfully applied the task runner.
|
||||||
if (trade.isWithdrawn()) {
|
if (trade.isCompleted()) {
|
||||||
processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(mailboxMessage);
|
processModel.getP2PService().getMailboxMessageService().removeMailboxMsg(mailboxMessage);
|
||||||
log.info("Remove {} from the P2P network as trade is already completed.",
|
log.info("Remove {} from the P2P network as trade is already completed.",
|
||||||
tradeMessage.getClass().getSimpleName());
|
tradeMessage.getClass().getSimpleName());
|
||||||
|
@ -205,7 +221,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
onMailboxMessage(tradeMessage, mailboxMessage.getSenderNodeAddress());
|
onMailboxMessage(tradeMessage, mailboxMessage.getSenderNodeAddress());
|
||||||
} else if (mailboxMessage instanceof AckMessage) {
|
} else if (mailboxMessage instanceof AckMessage) {
|
||||||
AckMessage ackMessage = (AckMessage) mailboxMessage;
|
AckMessage ackMessage = (AckMessage) mailboxMessage;
|
||||||
if (!trade.isWithdrawn()) {
|
if (!trade.isCompleted()) {
|
||||||
// We only apply the msg if we have not already completed the trade
|
// We only apply the msg if we have not already completed the trade
|
||||||
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
onAckMessage(ackMessage, mailboxMessage.getSenderNodeAddress());
|
||||||
}
|
}
|
||||||
|
@ -227,8 +243,10 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
// Abstract
|
// Abstract
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
public abstract Class<? extends TradeTask>[] getDepsitsConfirmedTasks();
|
||||||
|
|
||||||
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
|
public void handleInitMultisigRequest(InitMultisigRequest request, NodeAddress sender) {
|
||||||
System.out.println(getClass().getCanonicalName() + ".handleInitMultisigRequest()");
|
System.out.println(getClass().getSimpleName() + ".handleInitMultisigRequest()");
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
latchTrade();
|
latchTrade();
|
||||||
|
@ -256,7 +274,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
|
public void handleSignContractRequest(SignContractRequest message, NodeAddress sender) {
|
||||||
System.out.println(getClass().getCanonicalName() + ".handleSignContractRequest() " + trade.getId());
|
System.out.println(getClass().getSimpleName() + ".handleSignContractRequest() " + trade.getId());
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
|
@ -292,7 +310,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
|
public void handleSignContractResponse(SignContractResponse message, NodeAddress sender) {
|
||||||
System.out.println(getClass().getCanonicalName() + ".handleSignContractResponse() " + trade.getId());
|
System.out.println(getClass().getSimpleName() + ".handleSignContractResponse() " + trade.getId());
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
|
@ -329,7 +347,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
|
public void handleDepositResponse(DepositResponse response, NodeAddress sender) {
|
||||||
System.out.println(getClass().getCanonicalName() + ".handleDepositResponse()");
|
System.out.println(getClass().getSimpleName() + ".handleDepositResponse()");
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
synchronized (trade) {
|
synchronized (trade) {
|
||||||
latchTrade();
|
latchTrade();
|
||||||
|
@ -358,25 +376,55 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
}).start();
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO (woodser): update to use fluent for consistency
|
public void handle(DepositsConfirmedMessage response, NodeAddress sender) {
|
||||||
public void handleUpdateMultisigRequest(UpdateMultisigRequest message, NodeAddress peer, ErrorMessageHandler errorMessageHandler) {
|
System.out.println(getClass().getSimpleName() + ".handle(DepositsConfirmedMessage)");
|
||||||
latchTrade();
|
new Thread(() -> {
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
synchronized (trade) {
|
||||||
processModel.setTradeMessage(message);
|
latchTrade();
|
||||||
TradeTaskRunner taskRunner = new TradeTaskRunner(trade,
|
expect(new Condition(trade)
|
||||||
() -> {
|
.with(response)
|
||||||
stopTimeout();
|
.from(sender))
|
||||||
handleTaskRunnerSuccess(peer, message, "handleUpdateMultisigRequest");
|
.setup(tasks(ProcessDepositsConfirmedMessage.class)
|
||||||
},
|
.using(new TradeTaskRunner(trade,
|
||||||
errorMessage -> {
|
() -> {
|
||||||
handleTaskRunnerFault(peer, message, errorMessage);
|
handleTaskRunnerSuccess(sender, response);
|
||||||
});
|
},
|
||||||
taskRunner.addTasks(
|
errorMessage -> {
|
||||||
ProcessUpdateMultisigRequest.class
|
handleTaskRunnerFault(sender, response, errorMessage);
|
||||||
);
|
})))
|
||||||
startTimeout(TRADE_TIMEOUT);
|
.executeTasks();
|
||||||
taskRunner.run();
|
awaitTradeLatch();
|
||||||
awaitTradeLatch();
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
// received by buyer and arbitrator
|
||||||
|
protected void handle(PaymentReceivedMessage message, NodeAddress peer) {
|
||||||
|
System.out.println(getClass().getSimpleName() + ".handle(PaymentReceivedMessage)");
|
||||||
|
if (!(trade instanceof BuyerTrade || trade instanceof ArbitratorTrade)) {
|
||||||
|
log.warn("Ignoring PaymentReceivedMessage since not buyer or arbitrator");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (trade instanceof ArbitratorTrade && !trade.isPayoutUnlocked()) trade.syncWallet(); // arbitrator syncs slowly after deposits confirmed
|
||||||
|
synchronized (trade) {
|
||||||
|
latchTrade();
|
||||||
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
|
processModel.setTradeMessage(message);
|
||||||
|
expect(anyPhase(trade instanceof ArbitratorTrade ? new Trade.Phase[] { Trade.Phase.DEPOSITS_UNLOCKED } : new Trade.Phase[] { Trade.Phase.PAYMENT_SENT, Trade.Phase.PAYMENT_RECEIVED })
|
||||||
|
.with(message)
|
||||||
|
.from(peer))
|
||||||
|
.setup(tasks(
|
||||||
|
ProcessPaymentReceivedMessage.class)
|
||||||
|
.using(new TradeTaskRunner(trade,
|
||||||
|
() -> {
|
||||||
|
handleTaskRunnerSuccess(peer, message);
|
||||||
|
},
|
||||||
|
errorMessage -> {
|
||||||
|
handleTaskRunnerFault(peer, message, errorMessage);
|
||||||
|
})))
|
||||||
|
.executeTasks(true);
|
||||||
|
awaitTradeLatch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -591,7 +639,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
// Private
|
// Private
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
private void handleTaskRunnerSuccess(NodeAddress sender, @Nullable TradeMessage message, String source) {
|
protected void handleTaskRunnerSuccess(NodeAddress sender, @Nullable TradeMessage message, String source) {
|
||||||
log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId());
|
log.info("TaskRunner successfully completed. Triggered from {}, tradeId={}", source, trade.getId());
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
sendAckMessage(sender, message, true, null);
|
sendAckMessage(sender, message, true, null);
|
||||||
|
@ -638,7 +686,7 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
|
|
||||||
protected void awaitTradeLatch() {
|
protected void awaitTradeLatch() {
|
||||||
if (tradeLatch == null) return;
|
if (tradeLatch == null) return;
|
||||||
TradeUtils.awaitLatch(tradeLatch);
|
HavenoUtils.awaitLatch(tradeLatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isMyMessage(NetworkEnvelope message) {
|
private boolean isMyMessage(NetworkEnvelope message) {
|
||||||
|
@ -653,4 +701,23 @@ public abstract class TradeProtocol implements DecryptedDirectMessageListener, D
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendDepositsConfirmedMessage() {
|
||||||
|
new Thread(() -> {
|
||||||
|
synchronized (trade) {
|
||||||
|
latchTrade();
|
||||||
|
expect(new Condition(trade))
|
||||||
|
.setup(tasks(getDepsitsConfirmedTasks())
|
||||||
|
.using(new TradeTaskRunner(trade,
|
||||||
|
() -> {
|
||||||
|
handleTaskRunnerSuccess(null, null, "SendDepositsConfirmedMessages");
|
||||||
|
},
|
||||||
|
(errorMessage) -> {
|
||||||
|
handleTaskRunnerFault(null, null, errorMessage);
|
||||||
|
})))
|
||||||
|
.executeTasks(true);
|
||||||
|
awaitTradeLatch();
|
||||||
|
}
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,6 @@ import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.daemon.model.MoneroTx;
|
import monero.daemon.model.MoneroTx;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
// Fields marked as transient are only used during protocol execution which are based on directMessages so we do not
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ArbitratorProcessPaymentAccountKeyRequest extends TradeTask {
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public ArbitratorProcessPaymentAccountKeyRequest(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// ensure deposit txs confirmed
|
|
||||||
trade.listenForDepositTxs();
|
|
||||||
if (trade.getPhase().ordinal() < Trade.Phase.DEPOSITS_CONFIRMED.ordinal()) {
|
|
||||||
throw new RuntimeException("Arbitrator refusing payment account key request for trade " + trade.getId() + " because the deposit txs have not confirmed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// create response for buyer with key to decrypt seller's payment account payload
|
|
||||||
PaymentAccountKeyResponse response = new PaymentAccountKeyResponse(
|
|
||||||
trade.getId(),
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
processModel.getPubKeyRing(),
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
Version.getP2PMessageVersion(),
|
|
||||||
trade.getSeller().getPaymentAccountKey(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
// send response to buyer
|
|
||||||
NodeAddress buyerAddress = trade.getBuyer().getNodeAddress();
|
|
||||||
log.info("Arbitrator sending PaymentAccountKeyResponse to buyer={}; offerId={}", buyerAddress, trade.getId());
|
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(buyerAddress, trade.getBuyer().getPubKeyRing(), response, new SendDirectMessageListener() {
|
|
||||||
@Override
|
|
||||||
public void onArrived() {
|
|
||||||
log.info("{} arrived: trading peer={}; offerId={}; uid={}", response.getClass().getSimpleName(), buyerAddress, trade.getId());
|
|
||||||
complete();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage) {
|
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), buyerAddress, trade.getId(), errorMessage);
|
|
||||||
appendToErrorMessage("Sending message failed: message=" + response + "\nerrorMessage=" + errorMessage);
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ArbitratorProcessPayoutTxPublishedMessage extends TradeTask {
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public ArbitratorProcessPayoutTxPublishedMessage(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
PayoutTxPublishedMessage request = (PayoutTxPublishedMessage) processModel.getTradeMessage();
|
|
||||||
|
|
||||||
// verify and publish payout tx
|
|
||||||
trade.verifyPayoutTx(request.getSignedPayoutTxHex(), false, true);
|
|
||||||
|
|
||||||
// update latest peer address
|
|
||||||
if (request.isMaker()) trade.getMaker().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
|
||||||
else trade.getTaker().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
|
||||||
|
|
||||||
// TODO: publish signed witness data?
|
|
||||||
//request.getSignedWitness()
|
|
||||||
|
|
||||||
// close arbitrator trade
|
|
||||||
processModel.getTradeManager().onTradeCompleted(trade);
|
|
||||||
complete();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -63,24 +63,25 @@ public class BuyerPreparePaymentSentMessage extends TradeTask {
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
||||||
|
|
||||||
// create payout tx if we have seller's updated multisig hex
|
// import multisig hex
|
||||||
if (trade.getTradingPeer().getUpdatedMultisigHex() != null) {
|
List<String> updatedMultisigHexes = new ArrayList<String>();
|
||||||
|
if (trade.getSeller().getUpdatedMultisigHex() != null) updatedMultisigHexes.add(trade.getSeller().getUpdatedMultisigHex());
|
||||||
|
if (trade.getArbitrator().getUpdatedMultisigHex() != null) updatedMultisigHexes.add(trade.getArbitrator().getUpdatedMultisigHex());
|
||||||
|
if (!updatedMultisigHexes.isEmpty()) {
|
||||||
|
multisigWallet.importMultisigHex(updatedMultisigHexes.toArray(new String[0])); // TODO (monero-project): fails if multisig hex imported individually
|
||||||
|
trade.saveWallet();
|
||||||
|
}
|
||||||
|
|
||||||
// create payout tx
|
// create payout tx if we have seller's updated multisig hex
|
||||||
|
if (trade.getSeller().getUpdatedMultisigHex() != null) {
|
||||||
|
|
||||||
|
// create payout tx
|
||||||
log.info("Buyer creating unsigned payout tx");
|
log.info("Buyer creating unsigned payout tx");
|
||||||
multisigWallet.importMultisigHex(trade.getTradingPeer().getUpdatedMultisigHex());
|
|
||||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||||
trade.setPayoutTx(payoutTx);
|
trade.setPayoutTx(payoutTx);
|
||||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||||
|
|
||||||
// start listening for published payout tx
|
|
||||||
trade.listenForPayoutTx();
|
|
||||||
} else {
|
|
||||||
if (trade.getSelf().getUpdatedMultisigHex() == null) trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
walletService.closeMultisigWallet(trade.getId());
|
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class BuyerProcessPaymentAccountKeyResponse extends TradeTask {
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public BuyerProcessPaymentAccountKeyResponse(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// update peer node address if not from arbitrator
|
|
||||||
if (!processModel.getTempTradingPeerNodeAddress().equals(trade.getArbitrator().getNodeAddress())) {
|
|
||||||
trade.getTradingPeer().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt peer's payment account payload
|
|
||||||
PaymentAccountKeyResponse request = (PaymentAccountKeyResponse) processModel.getTradeMessage();
|
|
||||||
if (trade.getTradingPeer().getPaymentAccountPayload() == null) {
|
|
||||||
trade.decryptPeersPaymentAccountPayload(request.getPaymentAccountKey());
|
|
||||||
}
|
|
||||||
|
|
||||||
// store updated multisig hex for processing on payment sent
|
|
||||||
if (request.getUpdatedMultisigHex() != null) trade.getTradingPeer().setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
|
||||||
|
|
||||||
// persist and complete
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
|
||||||
complete();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyRequest;
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class BuyerSendPaymentAccountKeyRequestToArbitrator extends TradeTask {
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public BuyerSendPaymentAccountKeyRequestToArbitrator(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// create request to arbitrator
|
|
||||||
PaymentAccountKeyRequest request = new PaymentAccountKeyRequest(
|
|
||||||
trade.getId(),
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
processModel.getPubKeyRing(),
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
Version.getP2PMessageVersion()
|
|
||||||
);
|
|
||||||
|
|
||||||
// send request to arbitrator
|
|
||||||
log.info("Sending {} with offerId {} and uid {} to arbitrator {} with pub key ring {}", request.getClass().getSimpleName(), request.getTradeId(), request.getUid(), trade.getArbitrator().getNodeAddress(), trade.getArbitrator().getPubKeyRing());
|
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(
|
|
||||||
trade.getArbitrator().getNodeAddress(),
|
|
||||||
trade.getArbitrator().getPubKeyRing(),
|
|
||||||
request,
|
|
||||||
new SendDirectMessageListener() {
|
|
||||||
@Override
|
|
||||||
public void onArrived() {
|
|
||||||
log.info("{} arrived at arbitrator: offerId={}", PaymentAccountKeyRequest.class.getSimpleName(), trade.getId());
|
|
||||||
complete();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage) {
|
|
||||||
log.warn("Failed to send {} to arbitrator, error={}.", PaymentAccountKeyRequest.class.getSimpleName(), errorMessage);
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -63,7 +63,7 @@ public class BuyerSendPaymentSentMessage extends SendMailboxMessageTask {
|
||||||
trade.getCounterCurrencyExtraData(),
|
trade.getCounterCurrencyExtraData(),
|
||||||
deterministicId,
|
deterministicId,
|
||||||
trade.getPayoutTxHex(),
|
trade.getPayoutTxHex(),
|
||||||
trade.getBuyer().getUpdatedMultisigHex(),
|
trade.getSelf().getUpdatedMultisigHex(),
|
||||||
trade.getSelf().getPaymentAccountKey()
|
trade.getSelf().getPaymentAccountKey()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.payment.PaymentAccount;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Slf4j
|
|
||||||
public class BuyerSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|
||||||
|
|
||||||
public BuyerSendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NodeAddress getReceiverNodeAddress() {
|
|
||||||
return trade.getArbitrator().getNodeAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PubKeyRing getReceiverPubKeyRing() {
|
|
||||||
return trade.getArbitrator().getPubKeyRing();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
|
||||||
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
|
|
||||||
return new PayoutTxPublishedMessage(
|
|
||||||
tradeId,
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
trade.isMaker(),
|
|
||||||
null, // TODO: send witness data?
|
|
||||||
trade.getPayoutTxHex()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateSent() {
|
|
||||||
log.info("Buyer sent PayoutTxPublishedMessage: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateArrived() {
|
|
||||||
log.info("Buyer's PayoutTxPublishedMessage arrived: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateStoredInMailbox() {
|
|
||||||
log.info("Buyer's PayoutTxPublishedMessage stored in mailbox: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateFault() {
|
|
||||||
log.error("Buyer's PayoutTxPublishedMessage failed: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -74,7 +74,7 @@ public class MaybeSendSignContractRequest extends TradeTask {
|
||||||
|
|
||||||
// create deposit tx and freeze inputs
|
// create deposit tx and freeze inputs
|
||||||
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
MoneroTxWallet depositTx = trade.getXmrWalletService().createDepositTx(trade);
|
||||||
|
|
||||||
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
|
// TODO (woodser): save frozen key images and unfreeze if trade fails before deposited to multisig
|
||||||
|
|
||||||
// save process state
|
// save process state
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
|
import bisq.core.trade.protocol.TradingPeer;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class ProcessDepositsConfirmedMessage extends TradeTask {
|
||||||
|
|
||||||
|
@SuppressWarnings({"unused"})
|
||||||
|
public ProcessDepositsConfirmedMessage(TaskRunner taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void run() {
|
||||||
|
try {
|
||||||
|
runInterceptHook();
|
||||||
|
|
||||||
|
// get sender based on the pub key
|
||||||
|
// TODO: trade.getTradingPeer(PubKeyRing)
|
||||||
|
DepositsConfirmedMessage request = (DepositsConfirmedMessage) processModel.getTradeMessage();
|
||||||
|
TradingPeer sender;
|
||||||
|
if (trade.getArbitrator().getPubKeyRing().equals(request.getPubKeyRing())) sender = trade.getArbitrator();
|
||||||
|
else if (trade.getBuyer().getPubKeyRing().equals(request.getPubKeyRing())) sender = trade.getBuyer();
|
||||||
|
else if (trade.getSeller().getPubKeyRing().equals(request.getPubKeyRing())) sender = trade.getSeller();
|
||||||
|
else throw new RuntimeException("Pub key ring is not from arbitrator, buyer, or seller");
|
||||||
|
|
||||||
|
// update peer node address
|
||||||
|
sender.setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||||
|
|
||||||
|
// decrypt seller payment account payload if key given
|
||||||
|
if (request.getSellerPaymentAccountKey() != null && trade.getTradingPeer().getPaymentAccountPayload() == null) {
|
||||||
|
log.info(trade.getClass().getSimpleName() + " decryping using seller payment account key: " + request.getSellerPaymentAccountKey());
|
||||||
|
trade.decryptPeerPaymentAccountPayload(request.getSellerPaymentAccountKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
// store updated multisig hex for processing on payment sent
|
||||||
|
sender.setUpdatedMultisigHex(request.getUpdatedMultisigHex());
|
||||||
|
|
||||||
|
// persist and complete
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
complete();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
failed(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -122,7 +122,7 @@ public class ProcessInitMultisigRequest extends TradeTask {
|
||||||
log.info("Importing exchanged multisig hex for trade {}", trade.getId());
|
log.info("Importing exchanged multisig hex for trade {}", trade.getId());
|
||||||
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword());
|
MoneroMultisigInitResult result = multisigWallet.exchangeMultisigKeys(Arrays.asList(peers[0].getExchangedMultisigHex(), peers[1].getExchangedMultisigHex()), xmrWalletService.getWalletPassword());
|
||||||
processModel.setMultisigAddress(result.getAddress());
|
processModel.setMultisigAddress(result.getAddress());
|
||||||
processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId()); // save and close multisig wallet once it's created
|
processModel.getProvider().getXmrWalletService().saveWallet(multisigWallet); // save multisig wallet once it's created
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
|
trade.setStateIfValidTransitionTo(Trade.State.MULTISIG_COMPLETED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ import bisq.core.offer.Offer;
|
||||||
import bisq.core.trade.ArbitratorTrade;
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.MakerTrade;
|
import bisq.core.trade.MakerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.trade.messages.InitTradeRequest;
|
import bisq.core.trade.messages.InitTradeRequest;
|
||||||
import bisq.core.trade.protocol.TradingPeer;
|
import bisq.core.trade.protocol.TradingPeer;
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ public class ProcessInitTradeRequest extends TradeTask {
|
||||||
if (!trade.getTaker().getNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree");
|
if (!trade.getTaker().getNodeAddress().equals(request.getTakerNodeAddress())) throw new RuntimeException("Init trade requests from maker and taker do not agree");
|
||||||
if (trade.getTaker().getPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
|
if (trade.getTaker().getPubKeyRing() != null) throw new RuntimeException("Pub key ring should not be initialized before processing InitTradeRequest");
|
||||||
trade.getTaker().setPubKeyRing(request.getPubKeyRing());
|
trade.getTaker().setPubKeyRing(request.getPubKeyRing());
|
||||||
if (!TradeUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
|
if (!HavenoUtils.isMakerSignatureValid(request, request.getMakerSignature(), offer.getPubKeyRing())) throw new RuntimeException("Maker signature is invalid for the trade request"); // verify maker signature
|
||||||
|
|
||||||
// check trade price
|
// check trade price
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -19,26 +19,22 @@ package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import bisq.core.account.sign.SignedWitness;
|
import bisq.core.account.sign.SignedWitness;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
|
import common.utils.GenUtils;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.wallet.MoneroWallet;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class BuyerProcessPaymentReceivedMessage extends TradeTask {
|
public class ProcessPaymentReceivedMessage extends TradeTask {
|
||||||
public BuyerProcessPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
public ProcessPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,39 +46,44 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
|
||||||
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
PaymentReceivedMessage message = (PaymentReceivedMessage) processModel.getTradeMessage();
|
||||||
Validator.checkTradeId(processModel.getOfferId(), message);
|
Validator.checkTradeId(processModel.getOfferId(), message);
|
||||||
checkNotNull(message);
|
checkNotNull(message);
|
||||||
checkArgument(message.getPayoutTxHex() != null);
|
checkArgument(message.getUnsignedPayoutTxHex() != null || message.getSignedPayoutTxHex() != null, "No payout tx hex provided");
|
||||||
|
|
||||||
// update to the latest peer address of our peer if the message is correct
|
// update to the latest peer address of our peer if the message is correct
|
||||||
trade.getTradingPeer().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
trade.getSeller().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||||
|
if (trade.getSeller().getNodeAddress().equals(trade.getBuyer().getNodeAddress())) trade.getBuyer().setNodeAddress(null); // tests sometimes reuse addresses
|
||||||
|
|
||||||
// handle if payout tx is not seen on network
|
// handle if payout tx not published
|
||||||
if (trade.getPhase().ordinal() < Trade.Phase.PAYOUT_PUBLISHED.ordinal()) {
|
if (!trade.isPayoutPublished()) {
|
||||||
|
|
||||||
// publish payout tx if signed. otherwise verify, sign, and publish payout tx
|
// import multisig hex
|
||||||
boolean previouslySigned = trade.getPayoutTxHex() != null;
|
MoneroWallet multisigWallet = trade.getWallet();
|
||||||
if (previouslySigned) {
|
if (message.getUpdatedMultisigHex() != null) {
|
||||||
log.info("Buyer publishing signed payout tx from seller");
|
multisigWallet.importMultisigHex(message.getUpdatedMultisigHex());
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
trade.saveWallet();
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId());
|
|
||||||
List<String> txHashes = multisigWallet.submitMultisigTxHex(message.getPayoutTxHex());
|
|
||||||
trade.setPayoutTx(multisigWallet.getTx(txHashes.get(0)));
|
|
||||||
XmrWalletService.printTxs("payoutTx received from peer", trade.getPayoutTx());
|
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG);
|
|
||||||
walletService.closeMultisigWallet(trade.getId());
|
|
||||||
} else {
|
|
||||||
log.info("Buyer verifying, signing, and publishing seller's payout tx");
|
|
||||||
trade.verifyPayoutTx(message.getPayoutTxHex(), true, true);
|
|
||||||
trade.setStateIfValidTransitionTo(Trade.State.BUYER_PUBLISHED_PAYOUT_TX);
|
|
||||||
// TODO (woodser): send PayoutTxPublishedMessage to seller
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// mark address entries as available
|
// arbitrator waits for buyer to sign and broadcast payout tx if message arrived
|
||||||
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
|
boolean isSigned = message.getSignedPayoutTxHex() != null;
|
||||||
|
if (trade instanceof ArbitratorTrade && !isSigned && message.isSawArrivedPaymentReceivedMsg()) {
|
||||||
|
log.info("{} waiting for buyer to sign and broadcast payout tx", trade.getClass().getSimpleName());
|
||||||
|
GenUtils.waitFor(30000);
|
||||||
|
multisigWallet.rescanSpent();
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify and publish payout tx
|
||||||
|
if (!trade.isPayoutPublished()) {
|
||||||
|
if (isSigned) {
|
||||||
|
log.info("{} publishing signed payout tx from seller", trade.getClass().getSimpleName());
|
||||||
|
trade.verifyPayoutTx(message.getSignedPayoutTxHex(), false, true);
|
||||||
|
} else {
|
||||||
|
log.info("{} verifying, signing, and publishing seller's payout tx", trade.getClass().getSimpleName());
|
||||||
|
trade.verifyPayoutTx(message.getUnsignedPayoutTxHex(), true, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.info("We got the payout tx already set from BuyerSetupPayoutTxListener and do nothing here. trade ID={}", trade.getId());
|
log.info("We got the payout tx already set from the payout listener and do nothing here. trade ID={}", trade.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove witness
|
|
||||||
SignedWitness signedWitness = message.getSignedWitness();
|
SignedWitness signedWitness = message.getSignedWitness();
|
||||||
if (signedWitness != null) {
|
if (signedWitness != null) {
|
||||||
// We received the signedWitness from the seller and publish the data to the network.
|
// We received the signedWitness from the seller and publish the data to the network.
|
||||||
|
@ -91,6 +92,8 @@ public class BuyerProcessPaymentReceivedMessage extends TradeTask {
|
||||||
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
|
processModel.getAccountAgeWitnessService().publishOwnSignedWitness(signedWitness);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// complete
|
||||||
|
if (!trade.isArbitrator()) trade.setStateIfValidTransitionTo(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG); // arbitrator trade completes on payout published
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
|
@ -72,9 +72,6 @@ public class ProcessSignContractResponse extends TradeTask {
|
||||||
// send deposit request when all contract signatures received
|
// send deposit request when all contract signatures received
|
||||||
if (processModel.getArbitrator().getContractSignature() != null && processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
|
if (processModel.getArbitrator().getContractSignature() != null && processModel.getMaker().getContractSignature() != null && processModel.getTaker().getContractSignature() != null) {
|
||||||
|
|
||||||
// start listening for deposit txs
|
|
||||||
trade.listenForDepositTxs();
|
|
||||||
|
|
||||||
// create request for arbitrator to deposit funds to multisig
|
// create request for arbitrator to deposit funds to multisig
|
||||||
DepositRequest request = new DepositRequest(
|
DepositRequest request = new DepositRequest(
|
||||||
trade.getOffer().getId(),
|
trade.getOffer().getId(),
|
||||||
|
|
|
@ -1,109 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
|
||||||
import bisq.core.trade.messages.UpdateMultisigResponse;
|
|
||||||
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import static bisq.core.util.Validator.checkTradeId;
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class ProcessUpdateMultisigRequest extends TradeTask {
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public ProcessUpdateMultisigRequest(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
log.debug("current trade state " + trade.getState());
|
|
||||||
UpdateMultisigRequest request = (UpdateMultisigRequest) processModel.getTradeMessage();
|
|
||||||
checkNotNull(request);
|
|
||||||
checkTradeId(processModel.getOfferId(), request);
|
|
||||||
MoneroWallet multisigWallet = processModel.getProvider().getXmrWalletService().getMultisigWallet(trade.getId());
|
|
||||||
|
|
||||||
System.out.println("PROCESS UPDATE MULTISIG REQUEST");
|
|
||||||
System.out.println(request);
|
|
||||||
|
|
||||||
// check if multisig wallet needs updated
|
|
||||||
if (!multisigWallet.isMultisigImportNeeded()) {
|
|
||||||
log.warn("Multisig wallet does not need updated, so request is unexpected");
|
|
||||||
failed(); // TODO (woodser): ignore instead fail
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get updated multisig hex
|
|
||||||
multisigWallet.sync();
|
|
||||||
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
|
||||||
|
|
||||||
// import the multisig hex
|
|
||||||
int numOutputsSigned = multisigWallet.importMultisigHex(request.getUpdatedMultisigHex());
|
|
||||||
System.out.println("Num outputs signed by imported multisig hex: " + numOutputsSigned);
|
|
||||||
|
|
||||||
// close multisig wallet
|
|
||||||
processModel.getProvider().getXmrWalletService().closeMultisigWallet(trade.getId());
|
|
||||||
|
|
||||||
// respond with updated multisig hex
|
|
||||||
UpdateMultisigResponse response = new UpdateMultisigResponse(
|
|
||||||
processModel.getOffer().getId(),
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
processModel.getPubKeyRing(),
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
Version.getP2PMessageVersion(),
|
|
||||||
new Date().getTime(),
|
|
||||||
updatedMultisigHex);
|
|
||||||
|
|
||||||
log.info("Send {} with offerId {} and uid {} to peer {}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid(), trade.getTradingPeer().getNodeAddress());
|
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeer().getNodeAddress(), trade.getTradingPeer().getPubKeyRing(), response, new SendDirectMessageListener() {
|
|
||||||
@Override
|
|
||||||
public void onArrived() {
|
|
||||||
log.info("{} arrived at trading peer: offerId={}; uid={}", response.getClass().getSimpleName(), response.getTradeId(), response.getUid());
|
|
||||||
complete();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage) {
|
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", response.getClass().getSimpleName(), response.getUid(), trade.getArbitrator().getNodeAddress(), errorMessage);
|
|
||||||
appendToErrorMessage("Sending response failed: response=" + response + "\nerrorMessage=" + errorMessage);
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
|
||||||
|
|
||||||
import bisq.common.crypto.PubKeyRing;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.PayoutTxPublishedMessage;
|
|
||||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
|
||||||
@Slf4j
|
|
||||||
public class SellerMaybeSendPayoutTxPublishedMessage extends SendMailboxMessageTask {
|
|
||||||
|
|
||||||
public SellerMaybeSendPayoutTxPublishedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// skip if payout tx not published
|
|
||||||
if (trade.getPhase().ordinal() < Trade.Phase.PAYOUT_PUBLISHED.ordinal()) {
|
|
||||||
complete();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
super.run();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NodeAddress getReceiverNodeAddress() {
|
|
||||||
return trade.getArbitrator().getNodeAddress();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PubKeyRing getReceiverPubKeyRing() {
|
|
||||||
return trade.getArbitrator().getPubKeyRing();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
|
||||||
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
|
|
||||||
return new PayoutTxPublishedMessage(
|
|
||||||
tradeId,
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
trade.isMaker(),
|
|
||||||
null, // TODO: send witness data?
|
|
||||||
trade.getPayoutTxHex()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateSent() {
|
|
||||||
log.info("Seller sent PayoutTxPublishedMessage: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateArrived() {
|
|
||||||
log.info("Seller's PayoutTxPublishedMessage arrived: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateStoredInMailbox() {
|
|
||||||
log.info("Seller's PayoutTxPublishedMessage stored in mailbox: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateFault() {
|
|
||||||
log.error("Seller's PayoutTxPublishedMessage failed: tradeId={} at arbitrator {}", trade.getId(), getReceiverNodeAddress());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,11 +17,16 @@
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import monero.wallet.MoneroWallet;
|
||||||
import monero.wallet.model.MoneroTxWallet;
|
import monero.wallet.model.MoneroTxWallet;
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
|
@ -37,25 +42,35 @@ public class SellerPreparePaymentReceivedMessage extends TradeTask {
|
||||||
try {
|
try {
|
||||||
runInterceptHook();
|
runInterceptHook();
|
||||||
|
|
||||||
|
// import multisig hex
|
||||||
|
MoneroWallet multisigWallet = trade.getWallet();
|
||||||
|
List<String> updatedMultisigHexes = new ArrayList<String>();
|
||||||
|
if (trade.getBuyer().getUpdatedMultisigHex() != null) updatedMultisigHexes.add(trade.getBuyer().getUpdatedMultisigHex());
|
||||||
|
if (trade.getArbitrator().getUpdatedMultisigHex() != null) updatedMultisigHexes.add(trade.getArbitrator().getUpdatedMultisigHex());
|
||||||
|
if (!updatedMultisigHexes.isEmpty()) {
|
||||||
|
multisigWallet.importMultisigHex(updatedMultisigHexes.toArray(new String[0]));
|
||||||
|
trade.saveWallet();
|
||||||
|
}
|
||||||
|
|
||||||
// verify, sign, and publish payout tx if given. otherwise create payout tx
|
// verify, sign, and publish payout tx if given. otherwise create payout tx
|
||||||
if (trade.getPayoutTxHex() != null) {
|
if (trade.getPayoutTxHex() != null) {
|
||||||
log.info("Seller verifying, signing, and publishing payout tx");
|
log.info("Seller verifying, signing, and publishing payout tx for trade {}", trade.getId());
|
||||||
trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true);
|
trade.verifyPayoutTx(trade.getPayoutTxHex(), true, true);
|
||||||
|
|
||||||
// mark address entries as available
|
|
||||||
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
// create unsigned payout tx
|
// create unsigned payout tx
|
||||||
log.info("Seller creating unsigned payout tx");
|
log.info("Seller creating unsigned payout tx for trade {}", trade.getId());
|
||||||
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
MoneroTxWallet payoutTx = trade.createPayoutTx();
|
||||||
System.out.println("created payout tx: " + payoutTx);
|
|
||||||
trade.setPayoutTx(payoutTx);
|
trade.setPayoutTx(payoutTx);
|
||||||
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
trade.setPayoutTxHex(payoutTx.getTxSet().getMultisigTxHex());
|
||||||
|
|
||||||
// start listening for published payout tx
|
// export multisig hex once
|
||||||
trade.listenForPayoutTx();
|
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
||||||
|
trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
|
|
@ -20,12 +20,10 @@ package bisq.core.trade.protocol.tasks;
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentSentMessage;
|
import bisq.core.trade.messages.PaymentSentMessage;
|
||||||
import bisq.core.util.Validator;
|
import bisq.core.util.Validator;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SellerProcessPaymentSentMessage extends TradeTask {
|
public class SellerProcessPaymentSentMessage extends TradeTask {
|
||||||
|
@ -47,18 +45,10 @@ public class SellerProcessPaymentSentMessage extends TradeTask {
|
||||||
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
trade.getBuyer().setUpdatedMultisigHex(message.getUpdatedMultisigHex());
|
||||||
|
|
||||||
// decrypt buyer's payment account payload
|
// decrypt buyer's payment account payload
|
||||||
trade.decryptPeersPaymentAccountPayload(message.getPaymentAccountKey());
|
trade.decryptPeerPaymentAccountPayload(message.getPaymentAccountKey());
|
||||||
|
|
||||||
// sync and update multisig wallet
|
|
||||||
if (trade.getBuyer().getUpdatedMultisigHex() != null) {
|
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // TODO: ensure sync() always called before importMultisigHex()
|
|
||||||
multisigWallet.importMultisigHex(trade.getBuyer().getUpdatedMultisigHex());
|
|
||||||
walletService.closeMultisigWallet(trade.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
// update latest peer address
|
// update latest peer address
|
||||||
trade.getTradingPeer().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
trade.getBuyer().setNodeAddress(processModel.getTempTradingPeerNodeAddress());
|
||||||
|
|
||||||
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
String counterCurrencyTxId = message.getCounterCurrencyTxId();
|
||||||
if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) {
|
if (counterCurrencyTxId != null && counterCurrencyTxId.length() < 100) {
|
||||||
|
@ -73,7 +63,6 @@ public class SellerProcessPaymentSentMessage extends TradeTask {
|
||||||
trade.setState(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
trade.setState(Trade.State.SELLER_RECEIVED_PAYMENT_SENT_MSG);
|
||||||
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
|
||||||
complete();
|
complete();
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
failed(t);
|
failed(t);
|
||||||
|
|
|
@ -21,6 +21,8 @@ import bisq.core.account.sign.SignedWitness;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentReceivedMessage;
|
import bisq.core.trade.messages.PaymentReceivedMessage;
|
||||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
import bisq.core.trade.messages.TradeMailboxMessage;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -30,13 +32,17 @@ import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
public abstract class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||||
SignedWitness signedWitness = null;
|
SignedWitness signedWitness = null;
|
||||||
|
|
||||||
public SellerSendPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
public SellerSendPaymentReceivedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected abstract NodeAddress getReceiverNodeAddress();
|
||||||
|
|
||||||
|
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void run() {
|
protected void run() {
|
||||||
try {
|
try {
|
||||||
|
@ -55,47 +61,52 @@ public class SellerSendPaymentReceivedMessage extends SendMailboxMessageTask {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TradeMailboxMessage getTradeMailboxMessage(String id) {
|
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||||
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
|
checkNotNull(trade.getPayoutTxHex(), "Payout tx must not be null");
|
||||||
|
|
||||||
|
// TODO: sign witness
|
||||||
|
// AccountAgeWitnessService accountAgeWitnessService = processModel.getAccountAgeWitnessService();
|
||||||
|
// if (accountAgeWitnessService.isSignWitnessTrade(trade)) {
|
||||||
|
// // Broadcast is done in accountAgeWitness domain.
|
||||||
|
// accountAgeWitnessService.traderSignAndPublishPeersAccountAgeWitness(trade).ifPresent(witness -> signedWitness = witness);
|
||||||
|
// }
|
||||||
|
|
||||||
return new PaymentReceivedMessage(
|
return new PaymentReceivedMessage(
|
||||||
id,
|
tradeId,
|
||||||
processModel.getMyNodeAddress(),
|
processModel.getMyNodeAddress(),
|
||||||
signedWitness,
|
signedWitness,
|
||||||
trade.getPayoutTxHex()
|
trade.isPayoutPublished() ? null : trade.getPayoutTxHex(), // unsigned
|
||||||
|
trade.isPayoutPublished() ? trade.getPayoutTxHex() : null, // signed
|
||||||
|
trade.getSelf().getUpdatedMultisigHex(),
|
||||||
|
trade.getState().ordinal() >= Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG.ordinal() // informs to expect payout
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: using PAYOUT_TX_PUBLISHED_MSG to represent PAYMENT_RECEIVED_MSG after payout, but PAYOUT_TX_PUBLISHED_MSG is specifically for arbitrator. delete *PAYOUT_TX_PUBLISHED* messages and check payout field manually?
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setStateSent() {
|
protected void setStateSent() {
|
||||||
trade.setState(trade.getState().ordinal() >= Trade.State.SELLER_PUBLISHED_PAYOUT_TX.ordinal() ? Trade.State.SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
trade.setStateIfProgress(Trade.State.SELLER_SENT_PAYMENT_RECEIVED_MSG);
|
||||||
log.info("Sent SellerReceivedPaymentMessage: tradeId={} at peer {} SignedWitness {}",
|
log.info("{} sent: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||||
trade.getId(), trade.getTradingPeer().getNodeAddress(), signedWitness);
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateArrived() {
|
|
||||||
trade.setState(trade.getState().ordinal() >= Trade.State.SELLER_PUBLISHED_PAYOUT_TX.ordinal() ? Trade.State.SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
|
||||||
log.info("Seller's PaymentReceivedMessage arrived: tradeId={} at peer {} SignedWitness {}",
|
|
||||||
trade.getId(), trade.getTradingPeer().getNodeAddress(), signedWitness);
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setStateStoredInMailbox() {
|
|
||||||
trade.setState(trade.getState().ordinal() >= Trade.State.SELLER_PUBLISHED_PAYOUT_TX.ordinal() ? Trade.State.SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
|
||||||
log.info("Seller's PaymentReceivedMessage stored in mailbox: tradeId={} at peer {} SignedWitness {}",
|
|
||||||
trade.getId(), trade.getTradingPeer().getNodeAddress(), signedWitness);
|
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void setStateFault() {
|
protected void setStateFault() {
|
||||||
trade.setState(trade.getState().ordinal() >= Trade.State.SELLER_PUBLISHED_PAYOUT_TX.ordinal() ? Trade.State.SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG : Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
trade.setStateIfProgress(Trade.State.SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG);
|
||||||
log.error("SellerReceivedPaymentMessage failed: tradeId={} at peer {} SignedWitness {}",
|
log.error("{} failed: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||||
trade.getId(), trade.getTradingPeer().getNodeAddress(), signedWitness);
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setStateStoredInMailbox() {
|
||||||
|
trade.setStateIfProgress(Trade.State.SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG);
|
||||||
|
log.info("{} stored in mailbox: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||||
|
processModel.getTradeManager().requestPersistence();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void setStateArrived() {
|
||||||
|
trade.setStateIfProgress(Trade.State.SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG);
|
||||||
|
log.info("{} arrived: tradeId={} at peer {} SignedWitness {}", getClass().getSimpleName(), trade.getId(), getReceiverNodeAddress(), signedWitness);
|
||||||
processModel.getTradeManager().requestPersistence();
|
processModel.getTradeManager().requestPersistence();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,26 +17,27 @@
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SetupDepositTxsListener extends TradeTask {
|
public class SellerSendPaymentReceivedMessageToArbitrator extends SellerSendPaymentReceivedMessage {
|
||||||
|
|
||||||
@SuppressWarnings({ "unused" })
|
public SellerSendPaymentReceivedMessageToArbitrator(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
public SetupDepositTxsListener(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
protected NodeAddress getReceiverNodeAddress() {
|
||||||
protected void run() {
|
return trade.getArbitrator().getNodeAddress();
|
||||||
try {
|
}
|
||||||
runInterceptHook();
|
|
||||||
trade.listenForDepositTxs();
|
protected PubKeyRing getReceiverPubKeyRing() {
|
||||||
complete();
|
return trade.getArbitrator().getPubKeyRing();
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.core.trade.messages.TradeMessage;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@Slf4j
|
||||||
|
public class SellerSendPaymentReceivedMessageToBuyer extends SellerSendPaymentReceivedMessage {
|
||||||
|
|
||||||
|
public SellerSendPaymentReceivedMessageToBuyer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected NodeAddress getReceiverNodeAddress() {
|
||||||
|
return trade.getBuyer().getNodeAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected PubKeyRing getReceiverPubKeyRing() {
|
||||||
|
return trade.getBuyer().getPubKeyRing();
|
||||||
|
}
|
||||||
|
|
||||||
|
// continue execution on fault so payment received message is sent to arbitrator
|
||||||
|
@Override
|
||||||
|
protected void onFault(String errorMessage, TradeMessage message) {
|
||||||
|
setStateFault();
|
||||||
|
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
||||||
|
complete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,23 +18,25 @@
|
||||||
package bisq.core.trade.protocol.tasks;
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
import bisq.core.btc.wallet.XmrWalletService;
|
||||||
|
import bisq.core.trade.BuyerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.messages.PaymentAccountKeyResponse;
|
import bisq.core.trade.messages.DepositsConfirmedMessage;
|
||||||
import bisq.core.trade.messages.TradeMailboxMessage;
|
import bisq.core.trade.messages.TradeMailboxMessage;
|
||||||
import bisq.common.app.Version;
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import monero.wallet.MoneroWallet;
|
import monero.wallet.MoneroWallet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow sender's payment account info to be decrypted when trade state is confirmed.
|
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class SellerSendPaymentAccountPayloadKey extends SendMailboxMessageTask {
|
public abstract class SendDepositsConfirmedMessage extends SendMailboxMessageTask {
|
||||||
private PaymentAccountKeyResponse message;
|
private DepositsConfirmedMessage message;
|
||||||
|
|
||||||
public SellerSendPaymentAccountPayloadKey(TaskRunner<Trade> taskHandler, Trade trade) {
|
public SendDepositsConfirmedMessage(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
super(taskHandler, trade);
|
super(taskHandler, trade);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,16 +49,22 @@ public class SellerSendPaymentAccountPayloadKey extends SendMailboxMessageTask {
|
||||||
failed(t);
|
failed(t);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected abstract NodeAddress getReceiverNodeAddress();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected abstract PubKeyRing getReceiverPubKeyRing();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
protected TradeMailboxMessage getTradeMailboxMessage(String tradeId) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
|
|
||||||
// get updated multisig hex
|
// export multisig hex once
|
||||||
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
if (trade.getSelf().getUpdatedMultisigHex() == null) {
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(tradeId);
|
MoneroWallet multisigWallet = walletService.getMultisigWallet(tradeId);
|
||||||
trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex()); // only export multisig hex once
|
trade.getSelf().setUpdatedMultisigHex(multisigWallet.exportMultisigHex());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
// We do not use a real unique ID here as we want to be able to re-send the exact same message in case the
|
||||||
|
@ -64,13 +72,12 @@ public class SellerSendPaymentAccountPayloadKey extends SendMailboxMessageTask {
|
||||||
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
// messages where only the one which gets processed by the peer would be removed we use the same uid. All
|
||||||
// other data stays the same when we re-send the message at any time later.
|
// other data stays the same when we re-send the message at any time later.
|
||||||
String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress();
|
String deterministicId = tradeId + processModel.getMyNodeAddress().getFullAddress();
|
||||||
message = new PaymentAccountKeyResponse(
|
message = new DepositsConfirmedMessage(
|
||||||
trade.getOffer().getId(),
|
trade.getOffer().getId(),
|
||||||
processModel.getMyNodeAddress(),
|
processModel.getMyNodeAddress(),
|
||||||
processModel.getPubKeyRing(),
|
processModel.getPubKeyRing(),
|
||||||
deterministicId,
|
deterministicId,
|
||||||
Version.getP2PMessageVersion(),
|
getReceiverNodeAddress().equals(trade.getBuyer().getNodeAddress()) ? trade.getSeller().getPaymentAccountKey() : null, // buyer receives seller's payment account decryption key
|
||||||
trade.getSelf().getPaymentAccountKey(),
|
|
||||||
trade.getSelf().getUpdatedMultisigHex());
|
trade.getSelf().getUpdatedMultisigHex());
|
||||||
}
|
}
|
||||||
return message;
|
return message;
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SendDepositsConfirmedMessageToArbitrator extends SendDepositsConfirmedMessage {
|
||||||
|
|
||||||
|
public SendDepositsConfirmedMessageToArbitrator(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeAddress getReceiverNodeAddress() {
|
||||||
|
return trade.getArbitrator().getNodeAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PubKeyRing getReceiverPubKeyRing() {
|
||||||
|
return trade.getArbitrator().getPubKeyRing();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SendDepositsConfirmedMessageToBuyer extends SendDepositsConfirmedMessage {
|
||||||
|
|
||||||
|
public SendDepositsConfirmedMessageToBuyer(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeAddress getReceiverNodeAddress() {
|
||||||
|
return trade.getBuyer().getNodeAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PubKeyRing getReceiverPubKeyRing() {
|
||||||
|
return trade.getBuyer().getPubKeyRing();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Haveno.
|
||||||
|
*
|
||||||
|
* Haveno is free software: you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or (at
|
||||||
|
* your option) any later version.
|
||||||
|
*
|
||||||
|
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
||||||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
||||||
|
* License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.core.trade.protocol.tasks;
|
||||||
|
|
||||||
|
import bisq.core.trade.Trade;
|
||||||
|
import bisq.network.p2p.NodeAddress;
|
||||||
|
import bisq.common.crypto.PubKeyRing;
|
||||||
|
import bisq.common.taskrunner.TaskRunner;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message on first confirmation to decrypt peer payment account and update multisig hex.
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class SendDepositsConfirmedMessageToSeller extends SendDepositsConfirmedMessage {
|
||||||
|
|
||||||
|
public SendDepositsConfirmedMessageToSeller(TaskRunner<Trade> taskHandler, Trade trade) {
|
||||||
|
super(taskHandler, trade);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NodeAddress getReceiverNodeAddress() {
|
||||||
|
return trade.getSeller().getNodeAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PubKeyRing getReceiverPubKeyRing() {
|
||||||
|
return trade.getSeller().getPubKeyRing();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import bisq.common.UserThread;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.fxmisc.easybind.EasyBind;
|
|
||||||
import org.fxmisc.easybind.Subscription;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class SetupPayoutTxListener extends TradeTask {
|
|
||||||
|
|
||||||
private Subscription tradeStateSubscription;
|
|
||||||
|
|
||||||
@SuppressWarnings({ "unused" })
|
|
||||||
public SetupPayoutTxListener(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// skip if payout already published
|
|
||||||
if (!trade.isPayoutPublished()) {
|
|
||||||
|
|
||||||
// listen for payout tx
|
|
||||||
trade.listenForPayoutTx();
|
|
||||||
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), newValue -> {
|
|
||||||
if (trade.isPayoutPublished()) {
|
|
||||||
|
|
||||||
// cleanup on trade completion
|
|
||||||
processModel.getXmrWalletService().resetAddressEntriesForPendingTrade(trade.getId());
|
|
||||||
UserThread.execute(this::unSubscribe); // unsubscribe
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
complete();
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unSubscribe() {
|
|
||||||
if (tradeStateSubscription != null) tradeStateSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* This file is part of Haveno.
|
|
||||||
*
|
|
||||||
* Haveno is free software: you can redistribute it and/or modify it
|
|
||||||
* under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or (at
|
|
||||||
* your option) any later version.
|
|
||||||
*
|
|
||||||
* Haveno is distributed in the hope that it will be useful, but WITHOUT
|
|
||||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
||||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
|
|
||||||
* License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with Haveno. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package bisq.core.trade.protocol.tasks;
|
|
||||||
|
|
||||||
import bisq.core.btc.wallet.XmrWalletService;
|
|
||||||
import bisq.core.trade.Trade;
|
|
||||||
import bisq.core.trade.messages.TradeMessage;
|
|
||||||
import bisq.core.trade.messages.UpdateMultisigRequest;
|
|
||||||
import bisq.core.trade.messages.UpdateMultisigResponse;
|
|
||||||
import bisq.core.trade.protocol.TradeListener;
|
|
||||||
|
|
||||||
import bisq.network.p2p.NodeAddress;
|
|
||||||
import bisq.network.p2p.SendDirectMessageListener;
|
|
||||||
|
|
||||||
import bisq.common.app.Version;
|
|
||||||
import bisq.common.taskrunner.TaskRunner;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import monero.wallet.MoneroWallet;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class UpdateMultisigWithTradingPeer extends TradeTask {
|
|
||||||
|
|
||||||
private TradeListener updateMultisigResponseListener;
|
|
||||||
|
|
||||||
@SuppressWarnings({"unused"})
|
|
||||||
public UpdateMultisigWithTradingPeer(TaskRunner taskHandler, Trade trade) {
|
|
||||||
super(taskHandler, trade);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void run() {
|
|
||||||
try {
|
|
||||||
runInterceptHook();
|
|
||||||
|
|
||||||
// fetch relevant trade info
|
|
||||||
XmrWalletService walletService = processModel.getProvider().getXmrWalletService();
|
|
||||||
MoneroWallet multisigWallet = walletService.getMultisigWallet(trade.getId()); // closed in BuyerPreparesPaymentStartedMessage
|
|
||||||
|
|
||||||
// skip if multisig wallet does not need updated
|
|
||||||
if (!multisigWallet.isMultisigImportNeeded()) {
|
|
||||||
log.warn("Multisig wallet does not need updated, this should not happen");
|
|
||||||
failed();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// register listener to receive updated multisig response
|
|
||||||
updateMultisigResponseListener = new TradeListener() {
|
|
||||||
@Override
|
|
||||||
public void onVerifiedTradeMessage(TradeMessage message, NodeAddress sender) {
|
|
||||||
if (!(message instanceof UpdateMultisigResponse)) return;
|
|
||||||
UpdateMultisigResponse response = (UpdateMultisigResponse) message;
|
|
||||||
multisigWallet.sync();
|
|
||||||
multisigWallet.importMultisigHex(response.getUpdatedMultisigHex());
|
|
||||||
trade.removeListener(updateMultisigResponseListener);
|
|
||||||
complete();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
trade.addListener(updateMultisigResponseListener);
|
|
||||||
|
|
||||||
// get updated multisig hex
|
|
||||||
multisigWallet.sync();
|
|
||||||
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
|
||||||
|
|
||||||
// message trading peer with updated multisig hex
|
|
||||||
UpdateMultisigRequest message = new UpdateMultisigRequest(
|
|
||||||
processModel.getOffer().getId(),
|
|
||||||
processModel.getMyNodeAddress(),
|
|
||||||
processModel.getPubKeyRing(),
|
|
||||||
UUID.randomUUID().toString(),
|
|
||||||
Version.getP2PMessageVersion(),
|
|
||||||
new Date().getTime(),
|
|
||||||
updatedMultisigHex);
|
|
||||||
|
|
||||||
System.out.println("Sending message: " + message);
|
|
||||||
|
|
||||||
// TODO (woodser): trade.getTradingPeer().getNodeAddress() and/or trade.getTradingPeer().getPubKeyRing() are null on restart of application, so cannot send payment to complete trade
|
|
||||||
log.info("Send {} with offerId {} and uid {} to peer {}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid(), trade.getTradingPeer().getNodeAddress());
|
|
||||||
processModel.getP2PService().sendEncryptedDirectMessage(trade.getTradingPeer().getNodeAddress(), trade.getTradingPeer().getPubKeyRing(), message, new SendDirectMessageListener() {
|
|
||||||
@Override
|
|
||||||
public void onArrived() {
|
|
||||||
log.info("{} arrived at trading peer: offerId={}; uid={}", message.getClass().getSimpleName(), message.getTradeId(), message.getUid());
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void onFault(String errorMessage) {
|
|
||||||
log.error("Sending {} failed: uid={}; peer={}; error={}", message.getClass().getSimpleName(), message.getUid(), trade.getArbitrator().getNodeAddress(), errorMessage);
|
|
||||||
appendToErrorMessage("Sending message failed: message=" + message + "\nerrorMessage=" + errorMessage);
|
|
||||||
failed();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (Throwable t) {
|
|
||||||
failed(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,8 +31,8 @@ import bisq.proto.grpc.GetTradeReply;
|
||||||
import bisq.proto.grpc.GetTradeRequest;
|
import bisq.proto.grpc.GetTradeRequest;
|
||||||
import bisq.proto.grpc.GetTradesReply;
|
import bisq.proto.grpc.GetTradesReply;
|
||||||
import bisq.proto.grpc.GetTradesRequest;
|
import bisq.proto.grpc.GetTradesRequest;
|
||||||
import bisq.proto.grpc.KeepFundsReply;
|
import bisq.proto.grpc.CompleteTradeReply;
|
||||||
import bisq.proto.grpc.KeepFundsRequest;
|
import bisq.proto.grpc.CompleteTradeRequest;
|
||||||
import bisq.proto.grpc.SendChatMessageReply;
|
import bisq.proto.grpc.SendChatMessageReply;
|
||||||
import bisq.proto.grpc.SendChatMessageRequest;
|
import bisq.proto.grpc.SendChatMessageRequest;
|
||||||
import bisq.proto.grpc.TakeOfferReply;
|
import bisq.proto.grpc.TakeOfferReply;
|
||||||
|
@ -176,13 +176,13 @@ class GrpcTradesService extends TradesImplBase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: rename KeepFundsRequest to CloseTradeRequest
|
// TODO: rename CompleteTradeRequest to CloseTradeRequest
|
||||||
@Override
|
@Override
|
||||||
public void keepFunds(KeepFundsRequest req,
|
public void completeTrade(CompleteTradeRequest req,
|
||||||
StreamObserver<KeepFundsReply> responseObserver) {
|
StreamObserver<CompleteTradeReply> responseObserver) {
|
||||||
try {
|
try {
|
||||||
coreApi.closeTrade(req.getTradeId());
|
coreApi.closeTrade(req.getTradeId());
|
||||||
var reply = KeepFundsReply.newBuilder().build();
|
var reply = CompleteTradeReply.newBuilder().build();
|
||||||
responseObserver.onNext(reply);
|
responseObserver.onNext(reply);
|
||||||
responseObserver.onCompleted();
|
responseObserver.onCompleted();
|
||||||
} catch (Throwable cause) {
|
} catch (Throwable cause) {
|
||||||
|
@ -244,12 +244,12 @@ 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(10, SECONDS));
|
put(getGetTradeMethod().getFullMethodName(), new GrpcCallRateMeter(20, SECONDS));
|
||||||
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetTradesMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getTakeOfferMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getConfirmPaymentStartedMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getConfirmPaymentReceivedMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getKeepFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
put(getCompleteTradeMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getWithdrawFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
put(getWithdrawFundsMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||||
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getGetChatMessagesMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
put(getSendChatMessageMethod().getFullMethodName(), new GrpcCallRateMeter(10, SECONDS));
|
||||||
|
|
|
@ -415,7 +415,7 @@ class GrpcWalletsService extends WalletsImplBase {
|
||||||
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(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(50, SECONDS));
|
put(getGetBalancesMethod().getFullMethodName(), new GrpcCallRateMeter(100, SECONDS)); // TODO: why do tests make so many calls to get balances?
|
||||||
put(getGetAddressBalanceMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getGetAddressBalanceMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||||
put(getGetFundingAddressesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
put(getGetFundingAddressesMethod().getFullMethodName(), new GrpcCallRateMeter(1, SECONDS));
|
||||||
put(getSendBtcMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
put(getSendBtcMethod().getFullMethodName(), new GrpcCallRateMeter(1, MINUTES));
|
||||||
|
|
|
@ -28,7 +28,7 @@ import bisq.core.offer.placeoffer.tasks.MakerReserveOfferFunds;
|
||||||
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
import bisq.core.offer.placeoffer.tasks.ValidateOffer;
|
||||||
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
import bisq.core.trade.protocol.tasks.ApplyFilter;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.BuyerPreparePaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerProcessPaymentReceivedMessage;
|
import bisq.core.trade.protocol.tasks.ProcessPaymentReceivedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.BuyerSendPaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.MakerSetLockTime;
|
import bisq.core.trade.protocol.tasks.MakerSetLockTime;
|
||||||
import bisq.core.trade.protocol.tasks.RemoveOffer;
|
import bisq.core.trade.protocol.tasks.RemoveOffer;
|
||||||
|
@ -36,8 +36,7 @@ import bisq.core.trade.protocol.tasks.SellerPreparePaymentReceivedMessage;
|
||||||
import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
|
import bisq.core.trade.protocol.tasks.SellerProcessPaymentSentMessage;
|
||||||
import bisq.core.trade.protocol.tasks.SellerPublishDepositTx;
|
import bisq.core.trade.protocol.tasks.SellerPublishDepositTx;
|
||||||
import bisq.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
import bisq.core.trade.protocol.tasks.SellerPublishTradeStatistics;
|
||||||
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessage;
|
import bisq.core.trade.protocol.tasks.SellerSendPaymentReceivedMessageToBuyer;
|
||||||
import bisq.core.trade.protocol.tasks.SetupPayoutTxListener;
|
|
||||||
import bisq.core.trade.protocol.tasks.TakerVerifyMakerFeePayment;
|
import bisq.core.trade.protocol.tasks.TakerVerifyMakerFeePayment;
|
||||||
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
import bisq.core.trade.protocol.tasks.VerifyPeersAccountAgeWitness;
|
||||||
import bisq.common.taskrunner.Task;
|
import bisq.common.taskrunner.Task;
|
||||||
|
@ -109,7 +108,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerVerifyMakerFeePayment.class,
|
||||||
SellerPreparePaymentReceivedMessage.class,
|
SellerPreparePaymentReceivedMessage.class,
|
||||||
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
||||||
SellerSendPaymentReceivedMessage.class
|
SellerSendPaymentReceivedMessageToBuyer.class
|
||||||
|
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
@ -123,10 +122,9 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
|
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
BuyerPreparePaymentSentMessage.class,
|
BuyerPreparePaymentSentMessage.class,
|
||||||
SetupPayoutTxListener.class,
|
|
||||||
BuyerSendPaymentSentMessage.class,
|
BuyerSendPaymentSentMessage.class,
|
||||||
|
|
||||||
BuyerProcessPaymentReceivedMessage.class
|
ProcessPaymentReceivedMessage.class
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -142,10 +140,9 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
TakerVerifyMakerFeePayment.class,
|
TakerVerifyMakerFeePayment.class,
|
||||||
BuyerPreparePaymentSentMessage.class,
|
BuyerPreparePaymentSentMessage.class,
|
||||||
SetupPayoutTxListener.class,
|
|
||||||
BuyerSendPaymentSentMessage.class,
|
BuyerSendPaymentSentMessage.class,
|
||||||
|
|
||||||
BuyerProcessPaymentReceivedMessage.class)
|
ProcessPaymentReceivedMessage.class)
|
||||||
));
|
));
|
||||||
addGroup("SellerAsMakerProtocol",
|
addGroup("SellerAsMakerProtocol",
|
||||||
FXCollections.observableArrayList(Arrays.asList(
|
FXCollections.observableArrayList(Arrays.asList(
|
||||||
|
@ -166,7 +163,7 @@ public class DebugView extends InitializableView<GridPane, Void> {
|
||||||
ApplyFilter.class,
|
ApplyFilter.class,
|
||||||
SellerPreparePaymentReceivedMessage.class,
|
SellerPreparePaymentReceivedMessage.class,
|
||||||
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
//SellerBroadcastPayoutTx.class, // TODO (woodser): removed from main pipeline; debug view?
|
||||||
SellerSendPaymentReceivedMessage.class
|
SellerSendPaymentReceivedMessageToBuyer.class
|
||||||
)
|
)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,7 +42,7 @@ import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferDirection;
|
import bisq.core.offer.OfferDirection;
|
||||||
import bisq.core.offer.OpenOfferManager;
|
import bisq.core.offer.OpenOfferManager;
|
||||||
import bisq.core.provider.price.PriceFeedService;
|
import bisq.core.provider.price.PriceFeedService;
|
||||||
import bisq.core.trade.TradeUtils;
|
import bisq.core.trade.HavenoUtils;
|
||||||
import bisq.core.user.Preferences;
|
import bisq.core.user.Preferences;
|
||||||
import bisq.core.util.VolumeUtil;
|
import bisq.core.util.VolumeUtil;
|
||||||
|
|
||||||
|
|
|
@ -424,8 +424,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
case PAYMENT_RECEIVED:
|
case PAYMENT_RECEIVED:
|
||||||
appendMsg = Res.get("takeOffer.error.depositPublished");
|
appendMsg = Res.get("takeOffer.error.depositPublished");
|
||||||
break;
|
break;
|
||||||
case PAYOUT_PUBLISHED:
|
case COMPLETED:
|
||||||
case WITHDRAWN:
|
|
||||||
appendMsg = Res.get("takeOffer.error.payoutPublished");
|
appendMsg = Res.get("takeOffer.error.payoutPublished");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -444,7 +443,7 @@ class TakeOfferViewModel extends ActivatableWithDataModel<TakeOfferDataModel> im
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyTradeState() {
|
private void applyTradeState() {
|
||||||
if (trade.isTakerFeePublished()) {
|
if (trade.isDepositRequested()) {
|
||||||
if (takeOfferResultHandler != null)
|
if (takeOfferResultHandler != null)
|
||||||
takeOfferResultHandler.run();
|
takeOfferResultHandler.run();
|
||||||
|
|
||||||
|
|
|
@ -183,12 +183,11 @@ public class NotificationCenter {
|
||||||
|
|
||||||
private void onTradePhaseChanged(Trade trade, Trade.Phase phase) {
|
private void onTradePhaseChanged(Trade trade, Trade.Phase phase) {
|
||||||
String message = null;
|
String message = null;
|
||||||
if (trade.isPayoutPublished() && !trade.isWithdrawn()) {
|
if (trade.isPayoutPublished() && !trade.isCompleted()) {
|
||||||
message = Res.get("notification.trade.completed");
|
message = Res.get("notification.trade.completed");
|
||||||
} else {
|
} else {
|
||||||
if (trade instanceof MakerTrade &&
|
if (trade instanceof MakerTrade &&
|
||||||
phase.ordinal() == Trade.Phase.DEPOSITS_PUBLISHED.ordinal() ||
|
phase.ordinal() == Trade.Phase.DEPOSITS_PUBLISHED.ordinal()) {
|
||||||
phase.ordinal() == Trade.Phase.DEPOSITS_CONFIRMED.ordinal()) {
|
|
||||||
final String role = trade instanceof BuyerTrade ? Res.get("shared.seller") : Res.get("shared.buyer");
|
final String role = trade instanceof BuyerTrade ? Res.get("shared.seller") : Res.get("shared.buyer");
|
||||||
message = Res.get("notification.trade.accepted", role);
|
message = Res.get("notification.trade.accepted", role);
|
||||||
}
|
}
|
||||||
|
|
|
@ -590,7 +590,7 @@ public class DisputeSummaryWindow extends Overlay<DisputeSummaryWindow> {
|
||||||
Button cancelButton = tuple.second;
|
Button cancelButton = tuple.second;
|
||||||
|
|
||||||
closeTicketButton.setOnAction(e -> {
|
closeTicketButton.setOnAction(e -> {
|
||||||
disputesService.resolveDisputePayout(dispute, disputeResult, contract);
|
disputesService.applyDisputePayout(dispute, disputeResult, contract);
|
||||||
doClose(closeTicketButton);
|
doClose(closeTicketButton);
|
||||||
|
|
||||||
// if (dispute.getDepositTxSerialized() == null) {
|
// if (dispute.getDepositTxSerialized() == null) {
|
||||||
|
|
|
@ -200,7 +200,7 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
((BuyerProtocol) tradeManager.getTradeProtocol(trade)).onPaymentStarted(resultHandler, errorMessageHandler);
|
((BuyerProtocol) tradeManager.getTradeProtocol(trade)).onPaymentStarted(resultHandler, errorMessageHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onFiatPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
public void onPaymentReceived(ResultHandler resultHandler, ErrorMessageHandler errorMessageHandler) {
|
||||||
Trade trade = getTrade();
|
Trade trade = getTrade();
|
||||||
checkNotNull(trade, "trade must not be null");
|
checkNotNull(trade, "trade must not be null");
|
||||||
checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade");
|
checkArgument(trade instanceof SellerTrade, "Trade must be instance of SellerTrade");
|
||||||
|
@ -466,7 +466,6 @@ public class PendingTradesDataModel extends ActivatableDataModel {
|
||||||
String payoutTxHashAsString = null;
|
String payoutTxHashAsString = null;
|
||||||
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
MoneroWallet multisigWallet = xmrWalletService.getMultisigWallet(trade.getId());
|
||||||
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
String updatedMultisigHex = multisigWallet.exportMultisigHex();
|
||||||
xmrWalletService.closeMultisigWallet(trade.getId()); // close multisig wallet
|
|
||||||
if (trade.getPayoutTxId() != null) {
|
if (trade.getPayoutTxId() != null) {
|
||||||
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
|
// payoutTxSerialized = payoutTx.bitcoinSerialize(); // TODO (woodser): no need to pass serialized txs for xmr
|
||||||
// payoutTxHashAsString = payoutTx.getHashAsString();
|
// payoutTxHashAsString = payoutTx.getHashAsString();
|
||||||
|
|
|
@ -30,8 +30,11 @@ import bisq.core.offer.Offer;
|
||||||
import bisq.core.offer.OfferUtil;
|
import bisq.core.offer.OfferUtil;
|
||||||
import bisq.core.provider.fee.FeeService;
|
import bisq.core.provider.fee.FeeService;
|
||||||
import bisq.core.provider.mempool.MempoolService;
|
import bisq.core.provider.mempool.MempoolService;
|
||||||
|
import bisq.core.trade.ArbitratorTrade;
|
||||||
|
import bisq.core.trade.BuyerTrade;
|
||||||
import bisq.core.trade.ClosedTradableManager;
|
import bisq.core.trade.ClosedTradableManager;
|
||||||
import bisq.core.trade.Contract;
|
import bisq.core.trade.Contract;
|
||||||
|
import bisq.core.trade.SellerTrade;
|
||||||
import bisq.core.trade.Trade;
|
import bisq.core.trade.Trade;
|
||||||
import bisq.core.trade.TradeUtil;
|
import bisq.core.trade.TradeUtil;
|
||||||
import bisq.core.user.User;
|
import bisq.core.user.User;
|
||||||
|
@ -115,6 +118,7 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
@Getter
|
@Getter
|
||||||
private final ObjectProperty<MessageState> messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
private final ObjectProperty<MessageState> messageStateProperty = new SimpleObjectProperty<>(MessageState.UNDEFINED);
|
||||||
private Subscription tradeStateSubscription;
|
private Subscription tradeStateSubscription;
|
||||||
|
private Subscription payoutStateSubscription;
|
||||||
private Subscription messageStateSubscription;
|
private Subscription messageStateSubscription;
|
||||||
@Getter
|
@Getter
|
||||||
protected final IntegerProperty mempoolStatus = new SimpleIntegerProperty();
|
protected final IntegerProperty mempoolStatus = new SimpleIntegerProperty();
|
||||||
|
@ -160,6 +164,11 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
tradeStateSubscription = null;
|
tradeStateSubscription = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payoutStateSubscription != null) {
|
||||||
|
payoutStateSubscription.unsubscribe();
|
||||||
|
payoutStateSubscription = null;
|
||||||
|
}
|
||||||
|
|
||||||
if (messageStateSubscription != null) {
|
if (messageStateSubscription != null) {
|
||||||
messageStateSubscription.unsubscribe();
|
messageStateSubscription.unsubscribe();
|
||||||
messageStateSubscription = null;
|
messageStateSubscription = null;
|
||||||
|
@ -174,6 +183,12 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
buyerState.set(BuyerState.UNDEFINED);
|
buyerState.set(BuyerState.UNDEFINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payoutStateSubscription != null) {
|
||||||
|
payoutStateSubscription.unsubscribe();
|
||||||
|
sellerState.set(SellerState.UNDEFINED);
|
||||||
|
buyerState.set(BuyerState.UNDEFINED);
|
||||||
|
}
|
||||||
|
|
||||||
if (messageStateSubscription != null) {
|
if (messageStateSubscription != null) {
|
||||||
messageStateSubscription.unsubscribe();
|
messageStateSubscription.unsubscribe();
|
||||||
messageStateProperty.set(MessageState.UNDEFINED);
|
messageStateProperty.set(MessageState.UNDEFINED);
|
||||||
|
@ -184,6 +199,9 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
|
tradeStateSubscription = EasyBind.subscribe(trade.stateProperty(), state -> {
|
||||||
UserThread.execute(() -> onTradeStateChanged(state));
|
UserThread.execute(() -> onTradeStateChanged(state));
|
||||||
});
|
});
|
||||||
|
payoutStateSubscription = EasyBind.subscribe(trade.payoutStateProperty(), state -> {
|
||||||
|
UserThread.execute(() -> onPayoutStateChanged(state));
|
||||||
|
});
|
||||||
messageStateSubscription = EasyBind.subscribe(trade.getProcessModel().getPaymentStartedMessageStateProperty(), this::onMessageStateChanged);
|
messageStateSubscription = EasyBind.subscribe(trade.getProcessModel().getPaymentStartedMessageStateProperty(), this::onMessageStateChanged);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -399,6 +417,13 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
tradeState,
|
tradeState,
|
||||||
trade != null ? trade.getShortId() : "trade is null");
|
trade != null ? trade.getShortId() : "trade is null");
|
||||||
|
|
||||||
|
// arbitrator trade view only shows tx status
|
||||||
|
if (trade instanceof ArbitratorTrade) {
|
||||||
|
buyerState.set(BuyerState.STEP1);
|
||||||
|
sellerState.set(SellerState.STEP1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (tradeState) {
|
switch (tradeState) {
|
||||||
// preparation
|
// preparation
|
||||||
case PREPARATION:
|
case PREPARATION:
|
||||||
|
@ -414,9 +439,8 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
|
|
||||||
// deposit requested
|
// deposit requested
|
||||||
case SENT_PUBLISH_DEPOSIT_TX_REQUEST:
|
case SENT_PUBLISH_DEPOSIT_TX_REQUEST:
|
||||||
case SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST:
|
|
||||||
case STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST:
|
|
||||||
case SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST:
|
case SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST:
|
||||||
|
case SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST:
|
||||||
|
|
||||||
// deposit published
|
// deposit published
|
||||||
case ARBITRATOR_PUBLISHED_DEPOSIT_TXS:
|
case ARBITRATOR_PUBLISHED_DEPOSIT_TXS:
|
||||||
|
@ -456,29 +480,16 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
// seller step 4
|
// seller step 4
|
||||||
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: // UI action
|
case SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT: // UI action
|
||||||
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_PUBLISHED_PAYOUT_TX: // payout tx broadcasted
|
if (trade instanceof BuyerTrade) buyerState.set(BuyerState.STEP4);
|
||||||
case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG: // PAYOUT_TX_PUBLISHED_MSG sent
|
else if (trade instanceof SellerTrade) sellerState.set(SellerState.STEP3);
|
||||||
sellerState.set(SellerState.STEP3);
|
|
||||||
break;
|
break;
|
||||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG: // PAYOUT_TX_PUBLISHED_MSG arrived
|
|
||||||
case SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG: // PAYOUT_TX_PUBLISHED_MSG mailbox
|
|
||||||
case SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG: // PAYOUT_TX_PUBLISHED_MSG failed - payout tx is published, peer will see it in network so we ignore failure and complete
|
|
||||||
sellerState.set(SellerState.STEP4);
|
sellerState.set(SellerState.STEP4);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// buyer step 4
|
case TRADE_COMPLETED:
|
||||||
case BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG:
|
|
||||||
// Alternatively the maker could have seen the payout tx earlier before he received the PAYOUT_TX_PUBLISHED_MSG:
|
|
||||||
case PAYOUT_TX_SEEN_IN_NETWORK:
|
|
||||||
// Alternatively the buyer could fully sign and publish the payout tx
|
|
||||||
case BUYER_PUBLISHED_PAYOUT_TX:
|
|
||||||
buyerState.set(BuyerState.STEP4);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case WITHDRAW_COMPLETED:
|
|
||||||
sellerState.set(UNDEFINED);
|
sellerState.set(UNDEFINED);
|
||||||
buyerState.set(BuyerState.UNDEFINED);
|
buyerState.set(BuyerState.UNDEFINED);
|
||||||
break;
|
break;
|
||||||
|
@ -491,4 +502,21 @@ public class PendingTradesViewModel extends ActivatableWithDataModel<PendingTrad
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onPayoutStateChanged(Trade.PayoutState payoutState) {
|
||||||
|
log.info("UI payoutState={}, id={}",
|
||||||
|
payoutState,
|
||||||
|
trade != null ? trade.getShortId() : "trade is null");
|
||||||
|
|
||||||
|
if (trade instanceof ArbitratorTrade) return;
|
||||||
|
|
||||||
|
switch (payoutState) {
|
||||||
|
case PUBLISHED:
|
||||||
|
sellerState.set(SellerState.STEP4);
|
||||||
|
buyerState.set(BuyerState.STEP4);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,8 +123,6 @@ public class SellerStep3View extends TradeStepView {
|
||||||
busyAnimation.play();
|
busyAnimation.play();
|
||||||
statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes. Please wait..."));
|
statusLabel.setText(Res.get("Confirming payment received. This can take up to a few minutes. Please wait..."));
|
||||||
break;
|
break;
|
||||||
case SELLER_PUBLISHED_PAYOUT_TX:
|
|
||||||
case SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG:
|
|
||||||
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
case SELLER_SENT_PAYMENT_RECEIVED_MSG:
|
||||||
busyAnimation.play();
|
busyAnimation.play();
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||||
|
@ -135,16 +133,14 @@ public class SellerStep3View extends TradeStepView {
|
||||||
}, 10);
|
}, 10);
|
||||||
break;
|
break;
|
||||||
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
case SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG:
|
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.messageArrived"));
|
statusLabel.setText(Res.get("shared.messageArrived"));
|
||||||
break;
|
break;
|
||||||
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
case SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG:
|
||||||
case SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG:
|
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
statusLabel.setText(Res.get("shared.messageStoredInMailbox"));
|
||||||
break;
|
break;
|
||||||
case SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG:
|
case SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG:
|
||||||
// We get a popup and the trade closed, so we dont need to show anything here
|
// We get a popup and the trade closed, so we dont need to show anything here
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
statusLabel.setText("");
|
statusLabel.setText("");
|
||||||
|
@ -464,7 +460,7 @@ public class SellerStep3View extends TradeStepView {
|
||||||
busyAnimation.play();
|
busyAnimation.play();
|
||||||
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
statusLabel.setText(Res.get("shared.sendingConfirmation"));
|
||||||
|
|
||||||
model.dataModel.onFiatPaymentReceived(() -> {
|
model.dataModel.onPaymentReceived(() -> {
|
||||||
}, errorMessage -> {
|
}, errorMessage -> {
|
||||||
busyAnimation.stop();
|
busyAnimation.stop();
|
||||||
new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show();
|
new Popup().warning(Res.get("popup.warning.sendMsgFailed")).show();
|
||||||
|
|
|
@ -731,7 +731,7 @@ service Trades {
|
||||||
}
|
}
|
||||||
rpc ConfirmPaymentReceived (ConfirmPaymentReceivedRequest) returns (ConfirmPaymentReceivedReply) {
|
rpc ConfirmPaymentReceived (ConfirmPaymentReceivedRequest) returns (ConfirmPaymentReceivedReply) {
|
||||||
}
|
}
|
||||||
rpc KeepFunds (KeepFundsRequest) returns (KeepFundsReply) {
|
rpc CompleteTrade (CompleteTradeRequest) returns (CompleteTradeReply) {
|
||||||
}
|
}
|
||||||
rpc WithdrawFunds (WithdrawFundsRequest) returns (WithdrawFundsReply) {
|
rpc WithdrawFunds (WithdrawFundsRequest) returns (WithdrawFundsReply) {
|
||||||
}
|
}
|
||||||
|
@ -787,11 +787,11 @@ message GetTradesReply {
|
||||||
repeated TradeInfo trades = 1;
|
repeated TradeInfo trades = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message KeepFundsRequest {
|
message CompleteTradeRequest {
|
||||||
string trade_id = 1;
|
string trade_id = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message KeepFundsReply {
|
message CompleteTradeReply {
|
||||||
}
|
}
|
||||||
|
|
||||||
message WithdrawFundsRequest {
|
message WithdrawFundsRequest {
|
||||||
|
@ -837,15 +837,16 @@ message TradeInfo {
|
||||||
string state = 16;
|
string state = 16;
|
||||||
string phase = 17;
|
string phase = 17;
|
||||||
string period_state = 18;
|
string period_state = 18;
|
||||||
bool is_deposit_published = 19;
|
string payout_state = 19;
|
||||||
bool is_deposit_unlocked = 20;
|
bool is_deposit_published = 20;
|
||||||
bool is_payment_sent = 21;
|
bool is_deposit_unlocked = 21;
|
||||||
bool is_payment_received = 22;
|
bool is_payment_sent = 22;
|
||||||
bool is_payout_published = 23;
|
bool is_payment_received = 23;
|
||||||
bool is_completed = 24;
|
bool is_payout_published = 24;
|
||||||
string contract_as_json = 25;
|
bool is_completed = 25;
|
||||||
ContractInfo contract = 26;
|
string contract_as_json = 26;
|
||||||
string trade_volume = 27;
|
ContractInfo contract = 27;
|
||||||
|
string trade_volume = 28;
|
||||||
|
|
||||||
string maker_deposit_tx_id = 100;
|
string maker_deposit_tx_id = 100;
|
||||||
string taker_deposit_tx_id = 101;
|
string taker_deposit_tx_id = 101;
|
||||||
|
|
|
@ -74,17 +74,11 @@ message NetworkEnvelope {
|
||||||
SignContractResponse sign_contract_response = 1006;
|
SignContractResponse sign_contract_response = 1006;
|
||||||
DepositRequest deposit_request = 1007;
|
DepositRequest deposit_request = 1007;
|
||||||
DepositResponse deposit_response = 1008;
|
DepositResponse deposit_response = 1008;
|
||||||
PaymentAccountKeyRequest payment_account_key_request = 1009;
|
DepositsConfirmedMessage deposits_confirmed_message = 1009;
|
||||||
PaymentAccountKeyResponse payment_account_key_response = 1010;
|
PaymentSentMessage payment_sent_message = 1010;
|
||||||
PaymentSentMessage payment_sent_message = 1011;
|
PaymentReceivedMessage payment_received_message = 1011;
|
||||||
PaymentReceivedMessage payment_received_message = 1012;
|
ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1012;
|
||||||
PayoutTxPublishedMessage payout_tx_published_message = 1013;
|
ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1013;
|
||||||
ArbitratorPayoutTxRequest arbitrator_payout_tx_request = 1016;
|
|
||||||
ArbitratorPayoutTxResponse arbitrator_payout_tx_response = 1017;
|
|
||||||
|
|
||||||
// TODO: delete these
|
|
||||||
UpdateMultisigRequest update_multisig_request = 1018;
|
|
||||||
UpdateMultisigResponse update_multisig_response = 1019;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,37 +349,12 @@ message DepositResponse {
|
||||||
int64 current_date = 5;
|
int64 current_date = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PaymentAccountKeyRequest {
|
message DepositsConfirmedMessage {
|
||||||
string trade_id = 1;
|
string trade_id = 1;
|
||||||
NodeAddress sender_node_address = 2;
|
NodeAddress sender_node_address = 2;
|
||||||
PubKeyRing pub_key_ring = 3;
|
PubKeyRing pub_key_ring = 3;
|
||||||
string uid = 4;
|
string uid = 4;
|
||||||
}
|
bytes seller_payment_account_key = 5;
|
||||||
|
|
||||||
message PaymentAccountKeyResponse {
|
|
||||||
string trade_id = 1;
|
|
||||||
NodeAddress sender_node_address = 2;
|
|
||||||
PubKeyRing pub_key_ring = 3;
|
|
||||||
string uid = 4;
|
|
||||||
bytes payment_account_key = 5;
|
|
||||||
string updated_multisig_hex = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateMultisigRequest {
|
|
||||||
string trade_id = 1;
|
|
||||||
NodeAddress sender_node_address = 2;
|
|
||||||
PubKeyRing pub_key_ring = 3;
|
|
||||||
string uid = 4;
|
|
||||||
int64 current_date = 5;
|
|
||||||
string updated_multisig_hex = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UpdateMultisigResponse {
|
|
||||||
string trade_id = 1;
|
|
||||||
NodeAddress sender_node_address = 2;
|
|
||||||
PubKeyRing pub_key_ring = 3;
|
|
||||||
string uid = 4;
|
|
||||||
int64 current_date = 5;
|
|
||||||
string updated_multisig_hex = 6;
|
string updated_multisig_hex = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -454,16 +423,10 @@ message PaymentReceivedMessage {
|
||||||
NodeAddress sender_node_address = 2;
|
NodeAddress sender_node_address = 2;
|
||||||
string uid = 3;
|
string uid = 3;
|
||||||
SignedWitness signed_witness = 4; // Added in v1.4.0
|
SignedWitness signed_witness = 4; // Added in v1.4.0
|
||||||
string payout_tx_hex = 5;
|
string unsigned_payout_tx_hex = 5;
|
||||||
}
|
|
||||||
|
|
||||||
message PayoutTxPublishedMessage {
|
|
||||||
string trade_id = 1;
|
|
||||||
NodeAddress sender_node_address = 2;
|
|
||||||
bool is_maker = 3;
|
|
||||||
string uid = 4;
|
|
||||||
SignedWitness signed_witness = 5;
|
|
||||||
string signed_payout_tx_hex = 6;
|
string signed_payout_tx_hex = 6;
|
||||||
|
string updated_multisig_hex = 7;
|
||||||
|
bool saw_arrived_payment_received_msg = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ArbitratorPayoutTxRequest {
|
message ArbitratorPayoutTxRequest {
|
||||||
|
@ -1644,33 +1607,24 @@ message Trade {
|
||||||
CONTRACT_SIGNATURE_REQUESTED = 6;
|
CONTRACT_SIGNATURE_REQUESTED = 6;
|
||||||
CONTRACT_SIGNED = 7;
|
CONTRACT_SIGNED = 7;
|
||||||
SENT_PUBLISH_DEPOSIT_TX_REQUEST = 8;
|
SENT_PUBLISH_DEPOSIT_TX_REQUEST = 8;
|
||||||
SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST = 9;
|
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 9;
|
||||||
STORED_IN_MAILBOX_PUBLISH_DEPOSIT_TX_REQUEST = 10;
|
SAW_ARRIVED_PUBLISH_DEPOSIT_TX_REQUEST = 10;
|
||||||
SEND_FAILED_PUBLISH_DEPOSIT_TX_REQUEST = 11;
|
ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 11;
|
||||||
ARBITRATOR_PUBLISHED_DEPOSIT_TXS = 12;
|
DEPOSIT_TXS_SEEN_IN_NETWORK = 12;
|
||||||
DEPOSIT_TXS_SEEN_IN_NETWORK = 13;
|
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 13;
|
||||||
DEPOSIT_TXS_CONFIRMED_IN_BLOCKCHAIN = 14;
|
DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 14;
|
||||||
DEPOSIT_TXS_UNLOCKED_IN_BLOCKCHAIN = 15;
|
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 15;
|
||||||
BUYER_CONFIRMED_IN_UI_PAYMENT_SENT = 16;
|
BUYER_SENT_PAYMENT_SENT_MSG = 16;
|
||||||
BUYER_SENT_PAYMENT_SENT_MSG = 17;
|
BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 17;
|
||||||
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 18;
|
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 18;
|
||||||
BUYER_STORED_IN_MAILBOX_PAYMENT_SENT_MSG = 19;
|
BUYER_SAW_ARRIVED_PAYMENT_SENT_MSG = 19;
|
||||||
BUYER_SEND_FAILED_PAYMENT_SENT_MSG = 20;
|
SELLER_RECEIVED_PAYMENT_SENT_MSG = 20;
|
||||||
SELLER_RECEIVED_PAYMENT_SENT_MSG = 21;
|
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 21;
|
||||||
SELLER_CONFIRMED_IN_UI_PAYMENT_RECEIPT = 22;
|
SELLER_SENT_PAYMENT_RECEIVED_MSG = 22;
|
||||||
SELLER_SENT_PAYMENT_RECEIVED_MSG = 23;
|
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 23;
|
||||||
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 24;
|
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 24;
|
||||||
SELLER_STORED_IN_MAILBOX_PAYMENT_RECEIVED_MSG = 25;
|
SELLER_SAW_ARRIVED_PAYMENT_RECEIVED_MSG = 25;
|
||||||
SELLER_SEND_FAILED_PAYMENT_RECEIVED_MSG = 26;
|
TRADE_COMPLETED = 26;
|
||||||
SELLER_PUBLISHED_PAYOUT_TX = 27;
|
|
||||||
SELLER_SENT_PAYOUT_TX_PUBLISHED_MSG = 28;
|
|
||||||
SELLER_SAW_ARRIVED_PAYOUT_TX_PUBLISHED_MSG = 29;
|
|
||||||
SELLER_STORED_IN_MAILBOX_PAYOUT_TX_PUBLISHED_MSG = 30;
|
|
||||||
SELLER_SEND_FAILED_PAYOUT_TX_PUBLISHED_MSG = 31;
|
|
||||||
BUYER_RECEIVED_PAYOUT_TX_PUBLISHED_MSG = 32;
|
|
||||||
BUYER_PUBLISHED_PAYOUT_TX = 33;
|
|
||||||
PAYOUT_TX_SEEN_IN_NETWORK = 34;
|
|
||||||
WITHDRAW_COMPLETED = 35;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Phase {
|
enum Phase {
|
||||||
|
@ -1682,8 +1636,14 @@ message Trade {
|
||||||
DEPOSITS_UNLOCKED = 5;
|
DEPOSITS_UNLOCKED = 5;
|
||||||
PAYMENT_SENT = 6;
|
PAYMENT_SENT = 6;
|
||||||
PAYMENT_RECEIVED = 7;
|
PAYMENT_RECEIVED = 7;
|
||||||
PAYOUT_PUBLISHED = 8;
|
COMPLETED = 8;
|
||||||
WITHDRAWN = 9;
|
}
|
||||||
|
|
||||||
|
enum PayoutState {
|
||||||
|
UNPUBLISHED = 0;
|
||||||
|
PUBLISHED = 1;
|
||||||
|
CONFIRMED = 2;
|
||||||
|
UNLOCKED = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum DisputeState {
|
enum DisputeState {
|
||||||
|
@ -1718,23 +1678,24 @@ message Trade {
|
||||||
int64 take_offer_date = 9;
|
int64 take_offer_date = 9;
|
||||||
int64 price = 10;
|
int64 price = 10;
|
||||||
State state = 11;
|
State state = 11;
|
||||||
DisputeState dispute_state = 12;
|
PayoutState payout_state = 12;
|
||||||
TradePeriodState period_state = 13;
|
DisputeState dispute_state = 13;
|
||||||
Contract contract = 14;
|
TradePeriodState period_state = 14;
|
||||||
string contract_as_json = 15;
|
Contract contract = 15;
|
||||||
bytes contract_hash = 16;
|
string contract_as_json = 16;
|
||||||
NodeAddress arbitrator_node_address = 17;
|
bytes contract_hash = 17;
|
||||||
NodeAddress mediator_node_address = 18;
|
NodeAddress arbitrator_node_address = 18;
|
||||||
string error_message = 19;
|
NodeAddress mediator_node_address = 19;
|
||||||
string counter_currency_tx_id = 20;
|
string error_message = 20;
|
||||||
repeated ChatMessage chat_message = 21;
|
string counter_currency_tx_id = 21;
|
||||||
MediationResultState mediation_result_state = 22;
|
repeated ChatMessage chat_message = 22;
|
||||||
int64 lock_time = 23;
|
MediationResultState mediation_result_state = 23;
|
||||||
NodeAddress refund_agent_node_address = 24;
|
int64 lock_time = 24;
|
||||||
RefundResultState refund_result_state = 25;
|
NodeAddress refund_agent_node_address = 25;
|
||||||
string counter_currency_extra_data = 26;
|
RefundResultState refund_result_state = 26;
|
||||||
string asset_tx_proof_result = 27; // name of AssetTxProofResult enum
|
string counter_currency_extra_data = 27;
|
||||||
string uid = 28;
|
string asset_tx_proof_result = 28; // name of AssetTxProofResult enum
|
||||||
|
string uid = 29;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BuyerAsMakerTrade {
|
message BuyerAsMakerTrade {
|
||||||
|
|
Loading…
Reference in a new issue