Merge pull request #6247 from ghubstan/check-precondition-for-confirm-payment-sent-or-rcvd

Conditionally block API's send payment sent/rcvd msgs
This commit is contained in:
Christoph Atteneder 2022-06-20 20:36:53 +02:00 committed by GitHub
commit d5e0c53c09
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 282 additions and 68 deletions

View file

@ -2,6 +2,8 @@ package bisq.apitest.method.trade;
import bisq.proto.grpc.TradeInfo;
import io.grpc.StatusRuntimeException;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
@ -32,6 +34,7 @@ import bisq.cli.CliMain;
import bisq.cli.GrpcClient;
import bisq.cli.table.builder.TableBuilder;
@SuppressWarnings({"ConstantConditions", "unused"})
public class AbstractTradeTest extends AbstractOfferTest {
public static final ExpectedProtocolStatus EXPECTED_PROTOCOL_STATUS = new ExpectedProtocolStatus();
@ -40,10 +43,16 @@ public class AbstractTradeTest extends AbstractOfferTest {
@Getter
protected static String tradeId;
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () ->
isLongRunningTest
? 10
: 2;
protected final Function<TradeInfo, String> toTradeDetailTable = (trade) ->
new TableBuilder(TRADE_DETAIL_TBL, trade).build().toString();
protected final Function<GrpcClient, String> toUserName = (client) -> client.equals(aliceClient) ? "Alice" : "Bob";
protected final Function<GrpcClient, String> toUserName = (client) ->
client.equals(aliceClient)
? "Alice"
: "Bob";
@BeforeAll
public static void initStaticFixtures() {
@ -84,17 +93,17 @@ public class AbstractTradeTest extends AbstractOfferTest {
return trade;
}
protected final void waitForDepositConfirmation(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
protected final void waitForTakerDepositConfirmation(Logger log,
TestInfo testInfo,
GrpcClient takerClient,
String tradeId) {
Predicate<TradeInfo> isTradeInDepositConfirmedStateAndPhase = (t) ->
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
String userName = toUserName.apply(grpcClient);
String userName = toUserName.apply(takerClient);
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
TradeInfo trade = grpcClient.getTrade(tradeId);
TradeInfo trade = takerClient.getTrade(tradeId);
if (!isTradeInDepositConfirmedStateAndPhase.test(trade)) {
log.warn("{} still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
userName,
@ -117,6 +126,15 @@ public class AbstractTradeTest extends AbstractOfferTest {
}
}
protected final void verifyTakerDepositNotConfirmed(TradeInfo trade) {
if (trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for trade %s in STATE=%s PHASE=%s, deposit tx should NOT be confirmed yet.",
trade.getShortId(),
trade.getState(),
trade.getPhase()));
}
}
protected final void verifyTakerDepositConfirmed(TradeInfo trade) {
if (!trade.getIsDepositConfirmed()) {
fail(format("INVALID_PHASE for trade %s in STATE=%s PHASE=%s, deposit tx never confirmed.",
@ -126,7 +144,80 @@ public class AbstractTradeTest extends AbstractOfferTest {
}
}
protected final void waitForBuyerSeesPaymentInitiatedMessage(Logger log,
protected final void verifyPaymentSentMsgIsFromBtcBuyerPrecondition(Logger log, GrpcClient sellerClient) {
String userName = toUserName.apply(sellerClient);
log.debug("BTC seller {} incorrectly sends a confirmpaymentstarted message, for trade {}", userName, tradeId);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> sellerClient.confirmPaymentStarted(tradeId));
String expectedExceptionMessage = "FAILED_PRECONDITION: you are the seller, and not sending payment";
assertEquals(expectedExceptionMessage, exception.getMessage());
}
protected final void verifyPaymentReceivedMsgIsFromBtcSellerPrecondition(Logger log, GrpcClient buyerClient) {
String userName = toUserName.apply(buyerClient);
log.debug("BTC buyer {} incorrectly sends a confirmpaymentreceived message, for trade {}", userName, tradeId);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> buyerClient.confirmPaymentReceived(tradeId));
String expectedExceptionMessage = "FAILED_PRECONDITION: you are the buyer, and not receiving payment";
assertEquals(expectedExceptionMessage, exception.getMessage());
}
protected final void verifyPaymentSentMsgDepositTxConfirmedPrecondition(Logger log, GrpcClient buyerClient) {
var trade = buyerClient.getTrade(tradeId);
verifyTakerDepositNotConfirmed(trade);
String userName = toUserName.apply(buyerClient);
log.debug("BTC buyer {} sends a confirmpaymentstarted message before deposit tx is confirmed, for trade {}",
userName,
tradeId);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> buyerClient.confirmPaymentStarted(tradeId));
String expectedExceptionMessage =
format("FAILED_PRECONDITION: cannot send a payment started message for trade '%s'", tradeId);
String failureReason = format("Expected exception message to start with '%s'%n, but got '%s'",
expectedExceptionMessage,
exception.getMessage());
assertTrue(exception.getMessage().startsWith(expectedExceptionMessage), failureReason);
expectedExceptionMessage = format("until trade deposit tx '%s' is confirmed", trade.getDepositTxId());
assertTrue(exception.getMessage().contains(expectedExceptionMessage));
}
protected final void verifyPaymentReceivedMsgDepositTxConfirmedPrecondition(Logger log, GrpcClient sellerClient) {
var trade = sellerClient.getTrade(tradeId);
verifyTakerDepositNotConfirmed(trade);
String userName = toUserName.apply(sellerClient);
log.debug("BTC seller {} sends a confirmpaymentreceived message before deposit tx is confirmed, for trade {}",
userName,
tradeId);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> sellerClient.confirmPaymentReceived(tradeId));
String expectedExceptionMessage =
format("FAILED_PRECONDITION: cannot send a payment received message for trade '%s'", tradeId);
String failureReason = format("Expected exception message to start with '%s'%n, but got '%s'",
expectedExceptionMessage,
exception.getMessage());
assertTrue(exception.getMessage().startsWith(expectedExceptionMessage), failureReason);
expectedExceptionMessage = format("until trade deposit tx '%s' is confirmed", trade.getDepositTxId());
assertTrue(exception.getMessage().contains(expectedExceptionMessage));
}
protected final void verifyPaymentReceivedMsgAfterPaymentSentMsgPrecondition(Logger log, GrpcClient sellerClient) {
var trade = sellerClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
String userName = toUserName.apply(sellerClient);
log.debug("BTC seller {} sends a confirmpaymentreceived message before a payment started message has been sent, for trade {}",
userName,
tradeId);
Throwable exception = assertThrows(StatusRuntimeException.class, () -> sellerClient.confirmPaymentReceived(tradeId));
String expectedExceptionMessage =
format("FAILED_PRECONDITION: cannot send a payment received confirmation message for trade '%s'", tradeId);
String failureReason = format("Expected exception message to start with '%s'%n, but got '%s'",
expectedExceptionMessage,
exception.getMessage());
assertTrue(exception.getMessage().startsWith(expectedExceptionMessage), failureReason);
expectedExceptionMessage = "until after a trade payment started message has been sent";
assertTrue(exception.getMessage().contains(expectedExceptionMessage));
}
protected final void waitUntilBuyerSeesPaymentStartedMessage(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {
@ -153,7 +244,7 @@ public class AbstractTradeTest extends AbstractOfferTest {
}
}
protected final void waitForSellerSeesPaymentInitiatedMessage(Logger log,
protected final void waitUntilSellerSeesPaymentStartedMessage(Logger log,
TestInfo testInfo,
GrpcClient grpcClient,
String tradeId) {

View file

@ -46,6 +46,7 @@ import bisq.apitest.method.offer.AbstractOfferTest;
// https://github.com/ghubstan/bisq/blob/master/cli/src/main/java/bisq/cli/TradeFormat.java
@Deprecated // Bisq v1 protocol BSQ trades have been replaced by BSQ Swaps.
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -100,7 +101,7 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
alicesBsqOffers = aliceClient.getMyOffersSortedByDate(BSQ);
assertEquals(0, alicesBsqOffers.size());
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
genBtcBlocksThenWait(1, 2_500);
trade = bobClient.getTrade(tradeId);
@ -122,7 +123,7 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
genBtcBlocksThenWait(1, 2_500);
bobClient.confirmPaymentStarted(trade.getTradeId());
sleep(6000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -134,7 +135,7 @@ public class TakeBuyBSQOfferTest extends AbstractTradeTest {
@Order(3)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
sleep(2_000);
var trade = aliceClient.getTrade(tradeId);
verifyBsqPaymentHasBeenReceived(log, aliceClient, trade);

View file

@ -42,6 +42,7 @@ import static protobuf.OfferDirection.BUY;
import static protobuf.OpenOffer.State.AVAILABLE;
@Disabled
@SuppressWarnings("ConstantConditions")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeBuyBTCOfferTest extends AbstractTradeTest {
@ -82,11 +83,9 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
sleep(2_500); // Allow available offer to be removed from offer book.
alicesUsdOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), USD);
assertEquals(0, alicesUsdOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -96,13 +95,23 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
@Test
@Order(2)
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
public void testPaymentMessagingPreconditions(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
// Alice is maker / btc buyer, Bob is taker / btc seller.
// Verify payment sent and rcvd msgs are sent by the right peers: buyer and seller.
verifyPaymentSentMsgIsFromBtcBuyerPrecondition(log, bobClient);
verifyPaymentReceivedMsgIsFromBtcSellerPrecondition(log, aliceClient);
// Verify fiat payment sent and rcvd msgs cannot be sent before trade deposit tx is confirmed.
verifyPaymentSentMsgDepositTxConfirmedPrecondition(log, aliceClient);
verifyPaymentReceivedMsgDepositTxConfirmedPrecondition(log, bobClient);
// Now generate the BTC block to confirm the taker deposit tx.
genBtcBlocksThenWait(1, 2_500);
waitForTakerDepositConfirmation(log, testInfo, bobClient, tradeId);
// Verify the seller can only send a payment rcvd msg after the payment started msg.
verifyPaymentReceivedMsgAfterPaymentSentMsgPrecondition(log, bobClient);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -110,9 +119,23 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
@Test
@Order(3)
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForTakerDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6_000);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(4)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3_000);
@ -131,7 +154,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
}
@Test
@Order(4)
@Order(5)
public void testCloseTrade(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1_000);

View file

@ -145,7 +145,7 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
alicesOffers = aliceClient.getMyOffersSortedByDate(BUY.name(), BRL);
assertEquals(0, alicesOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
@ -182,10 +182,10 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
trade = aliceClient.getTrade(tradeId);
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
@ -199,7 +199,7 @@ public class TakeBuyBTCOfferWithNationalBankAcctTest extends AbstractTradeTest {
@Order(4)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
bobClient.confirmPaymentReceived(trade.getTradeId());
sleep(3_000);

View file

@ -47,6 +47,7 @@ import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.table.builder.TableBuilder;
@Disabled
@SuppressWarnings("ConstantConditions")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeBuyXMROfferTest extends AbstractTradeTest {
@ -89,11 +90,9 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId(), TRADE_FEE_CURRENCY_CODE);
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
assertEquals(0, alicesXmrOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -103,15 +102,39 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
@Test
@Order(2)
public void testPaymentMessagingPreconditions(final TestInfo testInfo) {
try {
// Alice is maker / xmr buyer (btc seller), Bob is taker / xmr seller (btc buyer).
// Verify payment sent and rcvd msgs are sent by the right peers: buyer and seller.
verifyPaymentSentMsgIsFromBtcBuyerPrecondition(log, aliceClient);
verifyPaymentReceivedMsgIsFromBtcSellerPrecondition(log, bobClient);
// Verify xmr payment sent and rcvd msgs cannot be sent before trade deposit tx is confirmed.
verifyPaymentSentMsgDepositTxConfirmedPrecondition(log, bobClient);
verifyPaymentReceivedMsgDepositTxConfirmedPrecondition(log, aliceClient);
// Now generate the BTC block to confirm the taker deposit tx.
genBtcBlocksThenWait(1, 2_500);
waitForTakerDepositConfirmation(log, testInfo, bobClient, tradeId);
// Verify the seller can only send a payment rcvd msg after the payment started msg.
verifyPaymentReceivedMsgAfterPaymentSentMsgPrecondition(log, aliceClient);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(3)
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
log.debug("Bob sends XMR payment to Alice for trade {}", trade.getTradeId());
bobClient.confirmPaymentStarted(trade.getTradeId());
log.debug("Bob sends XMR payment to Alice for trade {}", tradeId);
bobClient.confirmPaymentStarted(tradeId);
sleep(3500);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Buyer View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View (Payment Sent)", bobClient.getTrade(tradeId));
@ -121,18 +144,18 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
}
@Test
@Order(3)
@Order(4)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
sleep(2_000);
var trade = aliceClient.getTrade(tradeId);
// If we were trading BSQ, Alice would verify payment has been sent to her
// Bisq / BSQ wallet, but we can do no such checks for XMR payments.
// All XMR transfers are done outside Bisq.
log.debug("Alice verifies XMR payment was received from Bob, for trade {}", trade.getTradeId());
aliceClient.confirmPaymentReceived(trade.getTradeId());
log.debug("Alice verifies XMR payment was received from Bob, for trade {}", tradeId);
aliceClient.confirmPaymentReceived(tradeId);
sleep(3_000);
trade = aliceClient.getTrade(tradeId);
@ -150,7 +173,7 @@ public class TakeBuyXMROfferTest extends AbstractTradeTest {
}
@Test
@Order(4)
@Order(5)
public void testCloseTrade(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1_000);

View file

@ -46,6 +46,7 @@ import static protobuf.OfferDirection.BUY;
import bisq.apitest.method.offer.AbstractOfferTest;
import bisq.cli.table.builder.TableBuilder;
@Deprecated // Bisq v1 protocol BSQ trades have been replaced by BSQ Swaps.
@Disabled
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ -81,7 +82,7 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
alicesLegacyBsqAcct.getId(),
TRADE_FEE_CURRENCY_CODE);
log.debug("Alice's SELL BSQ (BUY BTC) Offer:\n{}", new TableBuilder(OFFER_TBL, alicesOffer).build());
genBtcBlocksThenWait(1, 4_000);
genBtcBlocksThenWait(1, 2_500);
var offerId = alicesOffer.getId();
assertTrue(alicesOffer.getIsCurrencyForMakerFeeBtc());
var alicesBsqOffers = aliceClient.getMyOffers(btcTradeDirection, BSQ);
@ -94,7 +95,7 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
alicesBsqOffers = aliceClient.getMyOffersSortedByDate(BSQ);
assertEquals(0, alicesBsqOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId));
@ -109,12 +110,12 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
sendBsqPayment(log, aliceClient, trade);
genBtcBlocksThenWait(1, 2_500);
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -126,7 +127,7 @@ public class TakeSellBSQOfferTest extends AbstractTradeTest {
@Order(3)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
sleep(2_000);
var trade = bobClient.getTrade(tradeId);

View file

@ -43,6 +43,7 @@ import static protobuf.Offer.State.OFFER_FEE_PAID;
import static protobuf.OfferDirection.SELL;
@Disabled
@SuppressWarnings("ConstantConditions")
@Slf4j
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class TakeSellBTCOfferTest extends AbstractTradeTest {
@ -86,10 +87,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
sleep(2_500); // Allow available offer to be removed from offer book.
var takeableUsdOffers = bobClient.getOffersSortedByDate(SELL.name(), USD);
assertEquals(0, takeableUsdOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Buyer View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Seller View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -99,13 +99,23 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
@Test
@Order(2)
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
public void testPaymentMessagingPreconditions(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
bobClient.confirmPaymentStarted(tradeId);
sleep(6_000);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
// Alice is maker / btc seller, Bob is taker / btc buyer.
// Verify payment sent and rcvd msgs are sent by the right peers: buyer and seller.
verifyPaymentSentMsgIsFromBtcBuyerPrecondition(log, aliceClient);
verifyPaymentReceivedMsgIsFromBtcSellerPrecondition(log, bobClient);
// Verify fiat payment sent and rcvd msgs cannot be sent before trade deposit tx is confirmed.
verifyPaymentSentMsgDepositTxConfirmedPrecondition(log, bobClient);
verifyPaymentReceivedMsgDepositTxConfirmedPrecondition(log, aliceClient);
// Now generate the BTC block to confirm the taker deposit tx.
genBtcBlocksThenWait(1, 2_500);
waitForTakerDepositConfirmation(log, testInfo, bobClient, tradeId);
// Verify the seller can only send a payment rcvd msg after the payment started msg.
verifyPaymentReceivedMsgAfterPaymentSentMsgPrecondition(log, aliceClient);
} catch (StatusRuntimeException e) {
fail(e);
}
@ -113,9 +123,23 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
@Test
@Order(3)
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
bobClient.confirmPaymentStarted(tradeId);
sleep(6_000);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(4)
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
var trade = aliceClient.getTrade(tradeId);
aliceClient.confirmPaymentReceived(trade.getTradeId());
@ -134,7 +158,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
}
@Test
@Order(4)
@Order(5)
public void testBobsBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1_000);

View file

@ -60,7 +60,7 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
@BeforeAll
public static void setUp() {
AbstractOfferTest.setUp();
AbstractOfferTest.setUp(false);
createXmrPaymentAccounts();
EXPECTED_PROTOCOL_STATUS.init();
}
@ -93,12 +93,9 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
var trade = takeAlicesOffer(offerId, bobsXmrAcct.getId(), TRADE_FEE_CURRENCY_CODE);
alicesXmrOffers = aliceClient.getMyOffersSortedByDate(XMR);
assertEquals(0, alicesXmrOffers.size());
genBtcBlocksThenWait(1, 2_500);
waitForDepositConfirmation(log, testInfo, bobClient, trade.getTradeId());
trade = bobClient.getTrade(tradeId);
verifyTakerDepositConfirmed(trade);
verifyTakerDepositNotConfirmed(trade);
logTrade(log, testInfo, "Alice's Maker/Seller View", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -108,15 +105,39 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
@Test
@Order(2)
public void testPaymentMessagingPreconditions(final TestInfo testInfo) {
try {
// Alice is maker / xmr seller (btc buyer), Bob is taker / xmr buyer (btc seller).
// Verify payment sent and rcvd msgs are sent by the right peers: buyer and seller.
verifyPaymentSentMsgIsFromBtcBuyerPrecondition(log, bobClient);
verifyPaymentReceivedMsgIsFromBtcSellerPrecondition(log, aliceClient);
// Verify xmr payment sent and rcvd msgs cannot be sent before trade deposit tx is confirmed.
verifyPaymentSentMsgDepositTxConfirmedPrecondition(log, aliceClient);
verifyPaymentReceivedMsgDepositTxConfirmedPrecondition(log, bobClient);
// Now generate the BTC block to confirm the taker deposit tx.
genBtcBlocksThenWait(1, 2_500);
waitForTakerDepositConfirmation(log, testInfo, bobClient, tradeId);
// Verify the seller can only send a payment rcvd msg after the payment started msg.
verifyPaymentReceivedMsgAfterPaymentSentMsgPrecondition(log, bobClient);
} catch (StatusRuntimeException e) {
fail(e);
}
}
@Test
@Order(3)
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
try {
var trade = aliceClient.getTrade(tradeId);
waitForDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
waitForTakerDepositConfirmation(log, testInfo, aliceClient, trade.getTradeId());
log.debug("Alice sends XMR payment to Bob for trade {}", trade.getTradeId());
aliceClient.confirmPaymentStarted(trade.getTradeId());
sleep(3500);
sleep(3_500);
waitForBuyerSeesPaymentInitiatedMessage(log, testInfo, aliceClient, tradeId);
waitUntilBuyerSeesPaymentStartedMessage(log, testInfo, aliceClient, tradeId);
logTrade(log, testInfo, "Alice's Maker/Seller View (Payment Sent)", aliceClient.getTrade(tradeId));
logTrade(log, testInfo, "Bob's Taker/Buyer View (Payment Sent)", bobClient.getTrade(tradeId));
} catch (StatusRuntimeException e) {
@ -125,10 +146,10 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
}
@Test
@Order(3)
@Order(4)
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
try {
waitForSellerSeesPaymentInitiatedMessage(log, testInfo, bobClient, tradeId);
waitUntilSellerSeesPaymentStartedMessage(log, testInfo, bobClient, tradeId);
var trade = bobClient.getTrade(tradeId);
sleep(2_000);
@ -154,7 +175,7 @@ public class TakeSellXMROfferTest extends AbstractTradeTest {
}
@Test
@Order(4)
@Order(5)
public void testAlicesBtcWithdrawalToExternalAddress(final TestInfo testInfo) {
try {
genBtcBlocksThenWait(1, 1_000);

View file

@ -78,6 +78,7 @@ public class LongRunningTradesTest extends AbstractTradeTest {
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
setLongRunningTest(true);
test.testTakeAlicesSellOffer(testInfo);
test.testPaymentMessagingPreconditions(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testBobsBtcWithdrawalToExternalAddress(testInfo);

View file

@ -20,6 +20,7 @@ package bisq.apitest.scenario;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
@ -55,6 +56,7 @@ public class TradeTest extends AbstractTradeTest {
public void testTakeBuyBTCOffer(final TestInfo testInfo) {
TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest();
test.testTakeAlicesBuyOffer(testInfo);
test.testPaymentMessagingPreconditions(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testCloseTrade(testInfo);
@ -65,11 +67,13 @@ public class TradeTest extends AbstractTradeTest {
public void testTakeSellBTCOffer(final TestInfo testInfo) {
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
test.testTakeAlicesSellOffer(testInfo);
test.testPaymentMessagingPreconditions(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
}
@Disabled
@Test
@Order(3)
public void testTakeBuyBSQOffer(final TestInfo testInfo) {
@ -91,6 +95,7 @@ public class TradeTest extends AbstractTradeTest {
test.testCloseTrade(testInfo);
}
@Disabled
@Test
@Order(5)
public void testTakeSellBSQOffer(final TestInfo testInfo) {
@ -107,6 +112,7 @@ public class TradeTest extends AbstractTradeTest {
TakeBuyXMROfferTest test = new TakeBuyXMROfferTest();
TakeBuyXMROfferTest.createXmrPaymentAccounts();
test.testTakeAlicesSellBTCForXMROffer(testInfo);
test.testPaymentMessagingPreconditions(testInfo);
test.testBobsConfirmPaymentStarted(testInfo);
test.testAlicesConfirmPaymentReceived(testInfo);
test.testCloseTrade(testInfo);
@ -118,6 +124,7 @@ public class TradeTest extends AbstractTradeTest {
TakeSellXMROfferTest test = new TakeSellXMROfferTest();
TakeBuyXMROfferTest.createXmrPaymentAccounts();
test.testTakeAlicesBuyBTCForXMROffer(testInfo);
test.testPaymentMessagingPreconditions(testInfo);
test.testAlicesConfirmPaymentStarted(testInfo);
test.testBobsConfirmPaymentReceived(testInfo);
test.testAlicesBtcWithdrawalToExternalAddress(testInfo);

View file

@ -17,6 +17,7 @@
package bisq.core.api;
import bisq.core.api.exception.FailedPreconditionException;
import bisq.core.api.exception.NotAvailableException;
import bisq.core.api.exception.NotFoundException;
import bisq.core.btc.model.AddressEntry;
@ -177,6 +178,13 @@ class CoreTradesService {
void confirmPaymentStarted(String tradeId) {
var trade = getTrade(tradeId);
if (isFollowingBuyerProtocol(trade)) {
if (!trade.isDepositConfirmed()) {
throw new FailedPreconditionException(
format("cannot send a payment started message for trade '%s'%n"
+ "until trade deposit tx '%s' is confirmed",
trade.getId(),
trade.getDepositTxId()));
}
var tradeProtocol = tradeManager.getTradeProtocol(trade);
((BuyerProtocol) tradeProtocol).onPaymentStarted(
() -> {
@ -186,15 +194,29 @@ class CoreTradesService {
}
);
} else {
throw new IllegalStateException("you are the seller and not sending payment");
throw new FailedPreconditionException("you are the seller, and not sending payment");
}
}
void confirmPaymentReceived(String tradeId) {
var trade = getTrade(tradeId);
if (isFollowingBuyerProtocol(trade)) {
throw new IllegalStateException("you are the buyer, and not receiving payment");
throw new FailedPreconditionException("you are the buyer, and not receiving payment");
} else {
if (!trade.isDepositConfirmed()) {
throw new FailedPreconditionException(
format("cannot send a payment received message for trade '%s'%n"
+ "until trade deposit tx '%s' is confirmed",
trade.getId(),
trade.getDepositTxId()));
}
if (!trade.isFiatSent()) {
throw new FailedPreconditionException(
format("cannot send a payment received confirmation message for trade '%s'%n"
+ "until after a trade payment started message has been sent",
trade.getId()));
}
var tradeProtocol = tradeManager.getTradeProtocol(trade);
((SellerProtocol) tradeProtocol).onPaymentReceived(
() -> {