mirror of
https://github.com/bisq-network/bisq.git
synced 2025-03-04 11:07:59 +01:00
Add LongRunningTradesTest
This new api testcase can run long series' of regtest trades by looping over modified TakeBuyBTCOfferTest and TakeSellBTCOfferTest cases. The purpose is to help reproduce problems and isolate bugs Bisq's core and api layers. LongRunningTradesTest is not enabled by default; it will not run in a default test environment (such a Travis CI). Enviornment variable LONG_RUNNING_TRADES_TEST_ENABLED must exist for the test to run. The env variable can be set in a bash shell before running the test case in a shell (using a gradle command), or the environment variable can be set in an Intellij test launcher's Evironment variables field. The modifed (short running) TakeBuyBTCOfferTest and TakeSellBTCOfferTest cases run as before. Changes include: - Add static boolean isLongRunningTest to AbstractOfferTest. - Add looping control Supplier maxTradeStateAndPhaseChecks to AbstractTradeTest. It uses the isLongRunningTest flag in the superclass to help define the wait times for trade state/phase changes during short and long running tests. - Made AbstractTradeTest assert(true, trade.isDepositPublished) conditional upon isLongRunningTest value. Long running trade test asserts have to be looser due to increasing latency of wallet, offer and trade operations in the server as the trade counts increase. - Overload ApiTestCase#startSupportingApps with additional flag: startSupportingAppsInDebugMode. Default is false. This makes starting background apps in debug mode a bit more convenient and self explanatory.
This commit is contained in:
parent
64d4c311fb
commit
1f28fc6836
6 changed files with 372 additions and 53 deletions
|
@ -55,11 +55,23 @@ public class MethodTest extends ApiTestCase {
|
||||||
boolean registerDisputeAgents,
|
boolean registerDisputeAgents,
|
||||||
boolean generateBtcBlock,
|
boolean generateBtcBlock,
|
||||||
Enum<?>... supportingApps) {
|
Enum<?>... supportingApps) {
|
||||||
|
startSupportingApps(callRateMeteringConfigFile,
|
||||||
|
registerDisputeAgents,
|
||||||
|
generateBtcBlock,
|
||||||
|
false,
|
||||||
|
supportingApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startSupportingApps(File callRateMeteringConfigFile,
|
||||||
|
boolean registerDisputeAgents,
|
||||||
|
boolean generateBtcBlock,
|
||||||
|
boolean startSupportingAppsInDebugMode,
|
||||||
|
Enum<?>... supportingApps) {
|
||||||
try {
|
try {
|
||||||
setUpScaffold(new String[]{
|
setUpScaffold(new String[]{
|
||||||
"--supportingApps", toNameList.apply(supportingApps),
|
"--supportingApps", toNameList.apply(supportingApps),
|
||||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||||
"--enableBisqDebugging", "false"
|
"--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false"
|
||||||
});
|
});
|
||||||
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
@ -70,13 +82,23 @@ public class MethodTest extends ApiTestCase {
|
||||||
public static void startSupportingApps(boolean registerDisputeAgents,
|
public static void startSupportingApps(boolean registerDisputeAgents,
|
||||||
boolean generateBtcBlock,
|
boolean generateBtcBlock,
|
||||||
Enum<?>... supportingApps) {
|
Enum<?>... supportingApps) {
|
||||||
|
startSupportingApps(registerDisputeAgents,
|
||||||
|
generateBtcBlock,
|
||||||
|
false,
|
||||||
|
supportingApps);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void startSupportingApps(boolean registerDisputeAgents,
|
||||||
|
boolean generateBtcBlock,
|
||||||
|
boolean startSupportingAppsInDebugMode,
|
||||||
|
Enum<?>... supportingApps) {
|
||||||
try {
|
try {
|
||||||
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
// Disable call rate metering where there is no callRateMeteringConfigFile.
|
||||||
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
File callRateMeteringConfigFile = defaultRateMeterInterceptorConfig();
|
||||||
setUpScaffold(new String[]{
|
setUpScaffold(new String[]{
|
||||||
"--supportingApps", toNameList.apply(supportingApps),
|
"--supportingApps", toNameList.apply(supportingApps),
|
||||||
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
"--callRateMeteringConfigPath", callRateMeteringConfigFile.getAbsolutePath(),
|
||||||
"--enableBisqDebugging", "false"
|
"--enableBisqDebugging", startSupportingAppsInDebugMode ? "true" : "false"
|
||||||
});
|
});
|
||||||
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
doPostStartup(registerDisputeAgents, generateBtcBlock);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.bitcoinj.utils.Fiat;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
|
||||||
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
@ -45,6 +46,9 @@ import bisq.apitest.method.MethodTest;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractOfferTest extends MethodTest {
|
public abstract class AbstractOfferTest extends MethodTest {
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
protected static boolean isLongRunningTest;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void setUp() {
|
public static void setUp() {
|
||||||
startSupportingApps(true,
|
startSupportingApps(true,
|
||||||
|
|
|
@ -2,6 +2,8 @@ package bisq.apitest.method.trade;
|
||||||
|
|
||||||
import bisq.proto.grpc.TradeInfo;
|
import bisq.proto.grpc.TradeInfo;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
|
@ -22,6 +24,8 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
// A Trade ID cache for use in @Test sequences.
|
// A Trade ID cache for use in @Test sequences.
|
||||||
protected static String tradeId;
|
protected static String tradeId;
|
||||||
|
|
||||||
|
protected final Supplier<Integer> maxTradeStateAndPhaseChecks = () -> isLongRunningTest ? 10 : 2;
|
||||||
|
|
||||||
@BeforeAll
|
@BeforeAll
|
||||||
public static void initStaticFixtures() {
|
public static void initStaticFixtures() {
|
||||||
EXPECTED_PROTOCOL_STATUS.init();
|
EXPECTED_PROTOCOL_STATUS.init();
|
||||||
|
@ -44,7 +48,10 @@ public class AbstractTradeTest extends AbstractOfferTest {
|
||||||
assertNotNull(trade);
|
assertNotNull(trade);
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.state.name(), trade.getState());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.state.name(), trade.getState());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.phase.name(), trade.getPhase());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.phase.name(), trade.getPhase());
|
||||||
|
|
||||||
|
if (!isLongRunningTest)
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositPublished, trade.getIsDepositPublished());
|
||||||
|
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositConfirmed());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isDepositConfirmed, trade.getIsDepositConfirmed());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatSent, trade.getIsFiatSent());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatSent, trade.getIsFiatSent());
|
||||||
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatReceived, trade.getIsFiatReceived());
|
assertEquals(EXPECTED_PROTOCOL_STATUS.isFiatReceived, trade.getIsFiatReceived());
|
||||||
|
|
|
@ -20,9 +20,12 @@ package bisq.apitest.method.trade;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
|
import bisq.proto.grpc.TradeInfo;
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
@ -39,6 +42,7 @@ import static bisq.core.trade.Trade.Phase.DEPOSIT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
import static bisq.core.trade.Trade.Phase.FIAT_SENT;
|
||||||
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
import static bisq.core.trade.Trade.Phase.PAYOUT_PUBLISHED;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.Trade.State.*;
|
||||||
|
import static java.lang.String.format;
|
||||||
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.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
@ -51,7 +55,7 @@ import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
// Alice is buyer, Bob is seller.
|
// Alice is maker/buyer, Bob is taker/seller.
|
||||||
|
|
||||||
// Maker and Taker fees are in BSQ.
|
// Maker and Taker fees are in BSQ.
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = "bsq";
|
private static final String TRADE_FEE_CURRENCY_CODE = "bsq";
|
||||||
|
@ -86,24 +90,48 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
// Cache the trade id for the other tests.
|
// Cache the trade id for the other tests.
|
||||||
tradeId = trade.getTradeId();
|
tradeId = trade.getTradeId();
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
genBtcBlocksThenWait(1, 4000);
|
||||||
alicesUsdOffers = aliceClient.getMyOffersSortedByDate("buy", "usd");
|
alicesUsdOffers = aliceClient.getMyOffersSortedByDate("buy", "usd");
|
||||||
assertEquals(0, alicesUsdOffers.size());
|
assertEquals(0, alicesUsdOffers.size());
|
||||||
|
|
||||||
|
if (!isLongRunningTest) {
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
trade = bobClient.getTrade(trade.getTradeId());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX)
|
EXPECTED_PROTOCOL_STATUS.setState(SELLER_PUBLISHED_DEPOSIT_TX)
|
||||||
.setPhase(DEPOSIT_PUBLISHED)
|
.setPhase(DEPOSIT_PUBLISHED)
|
||||||
.setDepositPublished(true);
|
.setDepositPublished(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
||||||
|
}
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
trade = bobClient.getTrade(trade.getTradeId());
|
||||||
|
|
||||||
|
if (!trade.getIsDepositConfirmed()) {
|
||||||
|
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getDepositTxId(),
|
||||||
|
i);
|
||||||
|
sleep(5000);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||||
.setPhase(DEPOSIT_CONFIRMED)
|
.setPhase(DEPOSIT_CONFIRMED)
|
||||||
.setDepositConfirmed(true);
|
.setDepositConfirmed(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade);
|
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trade.getIsDepositConfirmed()) {
|
||||||
|
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -114,16 +142,56 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
public void testAlicesConfirmPaymentStarted(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
var trade = aliceClient.getTrade(tradeId);
|
||||||
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
|
||||||
sleep(3000);
|
|
||||||
|
|
||||||
|
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||||
|
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name())
|
||||||
|
&& t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||||
|
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase());
|
||||||
|
// fail("Bad trade state and phase.");
|
||||||
|
sleep(1000 * 10);
|
||||||
trade = aliceClient.getTrade(tradeId);
|
trade = aliceClient.getTrade(tradeId);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
aliceClient.confirmPaymentStarted(trade.getTradeId());
|
||||||
|
sleep(6000);
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
trade = aliceClient.getTrade(tradeId);
|
||||||
|
|
||||||
|
if (!trade.getIsFiatSent()) {
|
||||||
|
log.warn("Alice still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||||
|
trade.getShortId(),
|
||||||
|
i);
|
||||||
|
sleep(5000);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
assertEquals(OFFER_FEE_PAID.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||||
.setPhase(FIAT_SENT)
|
.setPhase(FIAT_SENT)
|
||||||
.setFiatSent(true);
|
.setFiatSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
|
logTrade(log, testInfo, "Alice's view after confirming fiat payment sent", trade);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -134,6 +202,33 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
public void testBobsConfirmPaymentReceived(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = bobClient.getTrade(tradeId);
|
var trade = bobClient.getTrade(tradeId);
|
||||||
|
|
||||||
|
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||||
|
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||||
|
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase());
|
||||||
|
// fail("Bad trade state and phase.");
|
||||||
|
sleep(1000 * 10);
|
||||||
|
trade = bobClient.getTrade(tradeId);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, cannot confirm payment received.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
bobClient.confirmPaymentReceived(trade.getTradeId());
|
bobClient.confirmPaymentReceived(trade.getTradeId());
|
||||||
sleep(3000);
|
sleep(3000);
|
||||||
|
|
||||||
|
@ -146,6 +241,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
.setFiatReceived(true);
|
.setFiatReceived(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
|
logTrade(log, testInfo, "Bob's view after confirming fiat payment received", trade);
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -170,7 +266,7 @@ public class TakeBuyBTCOfferTest extends AbstractTradeTest {
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
logTrade(log, testInfo, "Alice's view after keeping funds", trade);
|
||||||
BtcBalanceInfo currentBalance = aliceClient.getBtcBalances();
|
BtcBalanceInfo currentBalance = aliceClient.getBtcBalances();
|
||||||
log.debug("{} Alice's current available balance: {} BTC",
|
log.info("{} Alice's current available balance: {} BTC",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatSatoshis(currentBalance.getAvailableBalance()));
|
formatSatoshis(currentBalance.getAvailableBalance()));
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
|
|
|
@ -20,9 +20,12 @@ package bisq.apitest.method.trade;
|
||||||
import bisq.core.payment.PaymentAccount;
|
import bisq.core.payment.PaymentAccount;
|
||||||
|
|
||||||
import bisq.proto.grpc.BtcBalanceInfo;
|
import bisq.proto.grpc.BtcBalanceInfo;
|
||||||
|
import bisq.proto.grpc.TradeInfo;
|
||||||
|
|
||||||
import io.grpc.StatusRuntimeException;
|
import io.grpc.StatusRuntimeException;
|
||||||
|
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Disabled;
|
import org.junit.jupiter.api.Disabled;
|
||||||
|
@ -36,6 +39,7 @@ import static bisq.cli.CurrencyFormat.formatSatoshis;
|
||||||
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
import static bisq.core.btc.wallet.Restrictions.getDefaultBuyerSecurityDepositAsPercent;
|
||||||
import static bisq.core.trade.Trade.Phase.*;
|
import static bisq.core.trade.Trade.Phase.*;
|
||||||
import static bisq.core.trade.Trade.State.*;
|
import static bisq.core.trade.Trade.State.*;
|
||||||
|
import static java.lang.String.format;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
@ -48,7 +52,7 @@ import static protobuf.OpenOffer.State.AVAILABLE;
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
|
|
||||||
// Alice is seller, Bob is buyer.
|
// Alice is maker/seller, Bob is taker/buyer.
|
||||||
|
|
||||||
// Maker and Taker fees are in BTC.
|
// Maker and Taker fees are in BTC.
|
||||||
private static final String TRADE_FEE_CURRENCY_CODE = "btc";
|
private static final String TRADE_FEE_CURRENCY_CODE = "btc";
|
||||||
|
@ -90,21 +94,44 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
var takeableUsdOffers = bobClient.getOffersSortedByDate("sell", "usd");
|
var takeableUsdOffers = bobClient.getOffersSortedByDate("sell", "usd");
|
||||||
assertEquals(0, takeableUsdOffers.size());
|
assertEquals(0, takeableUsdOffers.size());
|
||||||
|
|
||||||
|
if (!isLongRunningTest) {
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
trade = bobClient.getTrade(trade.getTradeId());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(BUYER_RECEIVED_DEPOSIT_TX_PUBLISHED_MSG)
|
||||||
.setPhase(DEPOSIT_PUBLISHED)
|
.setPhase(DEPOSIT_PUBLISHED)
|
||||||
.setDepositPublished(true);
|
.setDepositPublished(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
|
|
||||||
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
logTrade(log, testInfo, "Bob's view after taking offer and sending deposit", trade);
|
||||||
|
}
|
||||||
|
|
||||||
genBtcBlocksThenWait(1, 1000);
|
genBtcBlocksThenWait(1, 2500);
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
trade = bobClient.getTrade(trade.getTradeId());
|
trade = bobClient.getTrade(trade.getTradeId());
|
||||||
|
|
||||||
|
if (!trade.getIsDepositConfirmed()) {
|
||||||
|
log.warn("Bob still waiting on trade {} tx {}: DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN, attempt # {}",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getDepositTxId(),
|
||||||
|
i);
|
||||||
|
sleep(5000);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
EXPECTED_PROTOCOL_STATUS.setState(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN)
|
||||||
.setPhase(DEPOSIT_CONFIRMED)
|
.setPhase(DEPOSIT_CONFIRMED)
|
||||||
.setDepositConfirmed(true);
|
.setDepositConfirmed(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade);
|
logTrade(log, testInfo, "Bob's view after deposit is confirmed", trade);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!trade.getIsDepositConfirmed()) {
|
||||||
|
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, deposit tx was never confirmed.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -115,10 +142,44 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
public void testBobsConfirmPaymentStarted(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = bobClient.getTrade(tradeId);
|
var trade = bobClient.getTrade(tradeId);
|
||||||
bobClient.confirmPaymentStarted(tradeId);
|
|
||||||
sleep(3000);
|
|
||||||
|
|
||||||
|
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||||
|
t.getState().equals(DEPOSIT_CONFIRMED_IN_BLOCK_CHAIN.name()) && t.getPhase().equals(DEPOSIT_CONFIRMED.name());
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
log.warn("INVALID_PHASE for Bob's trade {} in STATE={} PHASE={}, cannot confirm payment started yet.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase());
|
||||||
|
// fail("Bad trade state and phase.");
|
||||||
|
sleep(1000 * 10);
|
||||||
trade = bobClient.getTrade(tradeId);
|
trade = bobClient.getTrade(tradeId);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
fail(format("INVALID_PHASE for Bob's trade %s in STATE=%s PHASE=%s, could not confirm payment started.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bobClient.confirmPaymentStarted(tradeId);
|
||||||
|
sleep(6000);
|
||||||
|
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
trade = bobClient.getTrade(tradeId);
|
||||||
|
|
||||||
|
if (!trade.getIsFiatSent()) {
|
||||||
|
log.warn("Bob still waiting for trade {} BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG, attempt # {}",
|
||||||
|
trade.getShortId(),
|
||||||
|
i);
|
||||||
|
sleep(5000);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
// Note: offer.state == available
|
// Note: offer.state == available
|
||||||
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
assertEquals(AVAILABLE.name(), trade.getOffer().getState());
|
||||||
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
EXPECTED_PROTOCOL_STATUS.setState(BUYER_SAW_ARRIVED_FIAT_PAYMENT_INITIATED_MSG)
|
||||||
|
@ -126,6 +187,9 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
.setFiatSent(true);
|
.setFiatSent(true);
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
|
logTrade(log, testInfo, "Bob's view after confirming fiat payment sent", trade);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
fail(e);
|
fail(e);
|
||||||
}
|
}
|
||||||
|
@ -136,6 +200,32 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
public void testAlicesConfirmPaymentReceived(final TestInfo testInfo) {
|
||||||
try {
|
try {
|
||||||
var trade = aliceClient.getTrade(tradeId);
|
var trade = aliceClient.getTrade(tradeId);
|
||||||
|
|
||||||
|
Predicate<TradeInfo> tradeStateAndPhaseCorrect = (t) ->
|
||||||
|
t.getState().equals(SELLER_RECEIVED_FIAT_PAYMENT_INITIATED_MSG.name())
|
||||||
|
&& (t.getPhase().equals(PAYOUT_PUBLISHED.name()) || t.getPhase().equals(FIAT_SENT.name()));
|
||||||
|
for (int i = 1; i <= maxTradeStateAndPhaseChecks.get(); i++) {
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
log.warn("INVALID_PHASE for Alice's trade {} in STATE={} PHASE={}, cannot confirm payment received yet.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase());
|
||||||
|
// fail("Bad trade state and phase.");
|
||||||
|
sleep(1000 * 10);
|
||||||
|
trade = aliceClient.getTrade(tradeId);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tradeStateAndPhaseCorrect.test(trade)) {
|
||||||
|
fail(format("INVALID_PHASE for Alice's trade %s in STATE=%s PHASE=%s, could not confirm payment received.",
|
||||||
|
trade.getShortId(),
|
||||||
|
trade.getState(),
|
||||||
|
trade.getPhase()));
|
||||||
|
}
|
||||||
|
|
||||||
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
aliceClient.confirmPaymentReceived(trade.getTradeId());
|
||||||
sleep(3000);
|
sleep(3000);
|
||||||
|
|
||||||
|
@ -173,7 +263,7 @@ public class TakeSellBTCOfferTest extends AbstractTradeTest {
|
||||||
verifyExpectedProtocolStatus(trade);
|
verifyExpectedProtocolStatus(trade);
|
||||||
logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade);
|
logTrade(log, testInfo, "Bob's view after withdrawing funds to external wallet", trade);
|
||||||
BtcBalanceInfo currentBalance = bobClient.getBtcBalances();
|
BtcBalanceInfo currentBalance = bobClient.getBtcBalances();
|
||||||
log.debug("{} Bob's current available balance: {} BTC",
|
log.info("{} Bob's current available balance: {} BTC",
|
||||||
testName(testInfo),
|
testName(testInfo),
|
||||||
formatSatoshis(currentBalance.getAvailableBalance()));
|
formatSatoshis(currentBalance.getAvailableBalance()));
|
||||||
} catch (StatusRuntimeException e) {
|
} catch (StatusRuntimeException e) {
|
||||||
|
|
|
@ -0,0 +1,100 @@
|
||||||
|
/*
|
||||||
|
* This file is part of Bisq.
|
||||||
|
*
|
||||||
|
* Bisq 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.
|
||||||
|
*
|
||||||
|
* Bisq 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 Bisq. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package bisq.apitest.scenario;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.MethodOrderer;
|
||||||
|
import org.junit.jupiter.api.Order;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestInfo;
|
||||||
|
import org.junit.jupiter.api.TestMethodOrder;
|
||||||
|
import org.junit.jupiter.api.condition.EnabledIf;
|
||||||
|
|
||||||
|
import static java.lang.System.getenv;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
import bisq.apitest.method.trade.AbstractTradeTest;
|
||||||
|
import bisq.apitest.method.trade.TakeBuyBTCOfferTest;
|
||||||
|
import bisq.apitest.method.trade.TakeSellBTCOfferTest;
|
||||||
|
|
||||||
|
@EnabledIf("envLongRunningTestEnabled")
|
||||||
|
@Slf4j
|
||||||
|
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
||||||
|
public class LongRunningTradesTest extends AbstractTradeTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(1)
|
||||||
|
public void testTradeLoop(final TestInfo testInfo) {
|
||||||
|
int numTrades = 0;
|
||||||
|
while (numTrades < 50) {
|
||||||
|
|
||||||
|
log.info("*******************************************************************");
|
||||||
|
log.info("Trade # {}", ++numTrades);
|
||||||
|
log.info("*******************************************************************");
|
||||||
|
|
||||||
|
EXPECTED_PROTOCOL_STATUS.init();
|
||||||
|
testTakeBuyBTCOffer(testInfo);
|
||||||
|
|
||||||
|
genBtcBlocksThenWait(1, 1000 * 15);
|
||||||
|
|
||||||
|
log.info("*******************************************************************");
|
||||||
|
log.info("Trade # {}", ++numTrades);
|
||||||
|
log.info("*******************************************************************");
|
||||||
|
|
||||||
|
EXPECTED_PROTOCOL_STATUS.init();
|
||||||
|
testTakeSellBTCOffer(testInfo);
|
||||||
|
|
||||||
|
genBtcBlocksThenWait(1, 1000 * 15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTakeBuyBTCOffer(final TestInfo testInfo) {
|
||||||
|
TakeBuyBTCOfferTest test = new TakeBuyBTCOfferTest();
|
||||||
|
setLongRunningTest(true);
|
||||||
|
test.testTakeAlicesBuyOffer(testInfo);
|
||||||
|
test.testAlicesConfirmPaymentStarted(testInfo);
|
||||||
|
test.testBobsConfirmPaymentReceived(testInfo);
|
||||||
|
test.testAlicesKeepFunds(testInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testTakeSellBTCOffer(final TestInfo testInfo) {
|
||||||
|
TakeSellBTCOfferTest test = new TakeSellBTCOfferTest();
|
||||||
|
setLongRunningTest(true);
|
||||||
|
test.testTakeAlicesSellOffer(testInfo);
|
||||||
|
test.testBobsConfirmPaymentStarted(testInfo);
|
||||||
|
test.testAlicesConfirmPaymentReceived(testInfo);
|
||||||
|
test.testBobsBtcWithdrawalToExternalAddress(testInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static boolean envLongRunningTestEnabled() {
|
||||||
|
String envName = "LONG_RUNNING_TRADES_TEST_ENABLED";
|
||||||
|
String envX = getenv(envName);
|
||||||
|
if (envX != null) {
|
||||||
|
log.info("Enabled, found {}.", envName);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
log.info("Skipped, no environment variable {} defined.", envName);
|
||||||
|
log.info("To enable on Mac OS or Linux:"
|
||||||
|
+ "\tIf running in terminal, export LONG_RUNNING_TRADES_TEST_ENABLED=true in bash shell."
|
||||||
|
+ "\tIf running in Intellij, set LONG_RUNNING_TRADES_TEST_ENABLED=true in launcher's Environment variables field.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue