diff --git a/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java b/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java index 26ef8179a..82fe47b38 100644 --- a/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java +++ b/xrpl4j-client/src/main/java/org/xrpl/xrpl4j/client/XrplClient.java @@ -287,15 +287,18 @@ protected Optional> getValida * Check if there missing ledgers in rippled in the given range. * * @param submittedLedgerSequence {@link LedgerIndex} at which the {@link Transaction} was submitted on. - * @param lastLedgerSequence he ledger index/sequence of type {@link UnsignedInteger} after which the transaction - * will expire and won't be applied to the ledger. + * @param lastLedgerSequence The ledger index/sequence of type {@link UnsignedInteger} after which the + * transaction will expire and won't be applied to the ledger. * * @return {@link Boolean} to indicate if there are gaps in the ledger range. */ protected boolean ledgerGapsExistBetween( final UnsignedLong submittedLedgerSequence, - final UnsignedLong lastLedgerSequence + UnsignedLong lastLedgerSequence ) { + Objects.requireNonNull(submittedLedgerSequence); + Objects.requireNonNull(lastLedgerSequence); + final ServerInfoResult serverInfo; try { serverInfo = this.serverInformation(); @@ -304,6 +307,11 @@ protected boolean ledgerGapsExistBetween( return true; // Assume ledger gaps exist so this can be retried. } + // Ensure the lastLedgerSequence is (at least) as large as submittedLedgerSequence + if (FluentCompareTo.is(lastLedgerSequence).lessThan(submittedLedgerSequence)) { + lastLedgerSequence = submittedLedgerSequence; + } + Range submittedToLast = Range.closed(submittedLedgerSequence, lastLedgerSequence); return serverInfo.info().completeLedgers().stream() .noneMatch(range -> range.encloses(submittedToLast)); @@ -369,8 +377,10 @@ public Finality isFinal( LOGGER.debug("Transaction with hash: {} has not expired yet, check again", transactionHash); return Finality.builder().finalityStatus(FinalityStatus.NOT_FINAL).build(); } else { - boolean isMissingLedgers = ledgerGapsExistBetween(UnsignedLong.valueOf(submittedOnLedgerIndex.toString()), - UnsignedLong.valueOf(lastLedgerSequence.toString())); + boolean isMissingLedgers = ledgerGapsExistBetween( + UnsignedLong.valueOf(submittedOnLedgerIndex.toString()), + UnsignedLong.valueOf(lastLedgerSequence.toString()) + ); if (isMissingLedgers) { LOGGER.debug("Transaction with hash: {} has expired and rippled is missing some to confirm if it" + " was validated", transactionHash); diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java index 84d3ce135..73a4e4829 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AbstractIT.java @@ -85,6 +85,7 @@ import java.security.KeyStore; import java.time.Duration; import java.time.Instant; +import java.time.temporal.ChronoUnit; import java.util.List; import java.util.Objects; import java.util.UUID; @@ -98,10 +99,10 @@ public abstract class AbstractIT { public static final Duration POLL_INTERVAL = Durations.ONE_HUNDRED_MILLISECONDS; - + public static final Duration AT_MOST_INTERVAL = Duration.of(30, ChronoUnit.SECONDS); public static final String SUCCESS_STATUS = TransactionResultCodes.TES_SUCCESS; - protected static XrplEnvironment xrplEnvironment = XrplEnvironment.getConfiguredEnvironment(); + protected static XrplEnvironment xrplEnvironment = XrplEnvironment.getNewConfiguredEnvironment(); protected final Logger logger = LoggerFactory.getLogger(this.getClass()); @@ -245,7 +246,7 @@ protected Finality scanForFinality( ) { return given() .pollInterval(POLL_INTERVAL) - .atMost(Durations.ONE_MINUTE.dividedBy(2)) + .atMost(AT_MOST_INTERVAL) .ignoreException(RuntimeException.class) .await() .until( @@ -268,7 +269,7 @@ protected Finality scanForFinality( protected T scanForResult(Supplier resultSupplier, Predicate condition) { return given() - .atMost(Durations.ONE_MINUTE.dividedBy(2)) + .atMost(AT_MOST_INTERVAL) .pollInterval(POLL_INTERVAL) .await() .until(() -> { @@ -284,7 +285,7 @@ protected T scanForResult(Supplier resultSupplier) { Objects.requireNonNull(resultSupplier); return given() .pollInterval(POLL_INTERVAL) - .atMost(Durations.ONE_MINUTE.dividedBy(2)) + .atMost(AT_MOST_INTERVAL) .ignoreException(RuntimeException.class) .await() .until(resultSupplier::get, is(notNullValue())); @@ -294,7 +295,7 @@ protected T scanForLedgerObject(Supplier ledgerObjec Objects.requireNonNull(ledgerObjectSupplier); return given() .pollInterval(POLL_INTERVAL) - .atMost(Durations.ONE_MINUTE.dividedBy(2)) + .atMost(AT_MOST_INTERVAL) .ignoreException(RuntimeException.class) .await() .until(ledgerObjectSupplier::get, is(notNullValue())); @@ -722,7 +723,7 @@ protected Instant getMinExpirationTime() { Instant now = Instant.now(); return closeTime.isBefore(now) ? now : closeTime; } - + private void logAccountCreation(Address address) { logger.info("Generated wallet with ClassicAddress={})", address); } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java index 497f149db..62f8fb79f 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountDeleteIT.java @@ -25,8 +25,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.EnabledIf; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; import org.xrpl.xrpl4j.crypto.keys.KeyPair; import org.xrpl.xrpl4j.crypto.keys.Seed; @@ -43,22 +45,49 @@ import org.xrpl.xrpl4j.model.transactions.EscrowCreate; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import org.xrpl.xrpl4j.tests.environment.LocalRippledEnvironment; -import org.xrpl.xrpl4j.tests.environment.XrplEnvironment; import java.time.Duration; /** - * An integration test that submits AccountDelete transactions that handle a successful usage along with - * examples of all failure cases. + * An integration test that submits AccountDelete transactions that handle a successful usage along with examples of all + * failure cases. * * @see "https://xrpl.org/accountset.html" */ -@DisabledIf(value = "shouldRun", disabledReason = "AccountDeleteIT only runs with local rippled nodes.") +@EnabledIf(value = "shouldRun", disabledReason = "AccountDeleteIT only runs with local rippled nodes.") class AccountDeleteIT extends AbstractIT { - static boolean shouldRun() { - return System.getProperty("useTestnet") != null || - System.getProperty("useDevnet") != null || - System.getProperty("useClioTestnet") != null; + + /** + * If any "real" testnet is being used (i.e., the environment specified is not a local one) then this test should not + * be run. + * + * @return {@code true} if test/dev/clio networks are the execution environment; {@code false} otherwise. + */ + private static boolean shouldRun() { + return System.getProperty("useTestnet") == null && + System.getProperty("useDevnet") == null && + System.getProperty("useClioTestnet") == null; + } + + /** + * This test requires the Ledger Acceptor to be disabled, in order to tightly control advancement of ledgers. Because + * of this, some of the tests do not execute when running against real networks (because controlling ledger + * advancement is not possible). + */ + @BeforeAll + static void setupTest() { + // Turn the LedgerAcceptor off + xrplEnvironment.stopLedgerAcceptor(); + } + + /** + * Because this test requires the Ledger Acceptor to be disabled, once the test completes, the Ledger Acceptor must be + * enabled again so that follow-on tests execute as expected. + */ + @AfterAll + static void cleanupTest() { + // Turn the LedgerAcceptor off + xrplEnvironment.startLedgerAcceptor(POLL_INTERVAL); } @Test @@ -66,23 +95,24 @@ void testAccountDeleteItFailsWith_TooSoon() throws JsonRpcClientErrorException, // create two accounts, one will be the destination in the tx KeyPair senderAccount = constructRandomAccount(); KeyPair receiverAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. - // get account info for the sequence number + // get sender account info for the sequence number AccountInfoResult accountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) ); // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(accountInfo.accountData().sequence()) - .destination(receiverAccount.publicKey().deriveAddress()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(accountInfo.accountData().sequence()) + .destination(receiverAccount.publicKey().deriveAddress()) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -95,23 +125,24 @@ void testAccountDeleteItFailsWith_TooSoon() throws JsonRpcClientErrorException, void testAccountDeleteItFailsWith_DestinationIsSource() throws JsonRpcClientErrorException, JsonProcessingException { // create one account, will be the sender & destination in the tx KeyPair senderAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. - // get account info for the sequence number + // get sender account info for the sequence number AccountInfoResult accountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) ); // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(accountInfo.accountData().sequence()) - .destination(senderAccount.publicKey().deriveAddress()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(accountInfo.accountData().sequence()) + .destination(senderAccount.publicKey().deriveAddress()) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -123,10 +154,10 @@ void testAccountDeleteItFailsWith_DestinationIsSource() throws JsonRpcClientErro @Test void testAccountDeleteItFailsWith_DestinationTagNeeded() throws JsonRpcClientErrorException, JsonProcessingException { // create two accounts, one will be the destination in the tx - KeyPair senderAccount = constructRandomAccount(); KeyPair receiverAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. - // get account info for the sequence number + // get receiver account info for the sequence number AccountInfoResult receiverAccountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress()) ); @@ -134,45 +165,53 @@ void testAccountDeleteItFailsWith_DestinationTagNeeded() throws JsonRpcClientErr // create, sign & submit REQUIRE_DEST AccountSet tx for receiver FeeResult feeResult = xrplClient.fee(); AccountSet accountSet = AccountSet.builder() - .account(receiverAccount.publicKey().deriveAddress()) - .fee(feeResult.drops().openLedgerFee()) - .sequence(receiverAccountInfo.accountData().sequence()) - .setFlag(AccountSet.AccountSetFlag.REQUIRE_DEST) - .signingPublicKey(receiverAccount.publicKey()) - .build(); + .account(receiverAccount.publicKey().deriveAddress()) + .fee(feeResult.drops().openLedgerFee()) + .sequence(receiverAccountInfo.accountData().sequence()) + .setFlag(AccountSet.AccountSetFlag.REQUIRE_DEST) + .signingPublicKey(receiverAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountSet = signatureService.sign( - receiverAccount.privateKey(), accountSet + receiverAccount.privateKey(), accountSet ); SubmitResult accountSetSubmitResult = xrplClient.submit(signedAccountSet); - assertThat(accountSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); assertThat(signedAccountSet.hash()).isEqualTo(accountSetSubmitResult.transactionResult().hash()); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. + // confirm flag was set TransactionResult accountSetTransactionResult = this.scanForResult(() -> - this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class) + this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class) ); + assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null)) + .isEqualTo(AccountSet.AccountSetFlag.REQUIRE_DEST); AccountInfoResult updatedReceiverAccountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress()) ); - - assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null)) - .isEqualTo(AccountSet.AccountSetFlag.REQUIRE_DEST); assertThat(updatedReceiverAccountInfo.accountData().flags().lsfRequireDestTag()).isTrue(); + KeyPair senderAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledge + + // get sender account info for the sequence number + AccountInfoResult senderAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) + ); + // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(receiverAccountInfo.accountData().sequence()) - .destination(receiverAccount.publicKey().deriveAddress()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(senderAccountInfo.accountData().sequence()) + .destination(receiverAccount.publicKey().deriveAddress()) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -183,11 +222,11 @@ void testAccountDeleteItFailsWith_DestinationTagNeeded() throws JsonRpcClientErr @Test void testAccountDeleteItFailsWith_NoPermission() throws JsonRpcClientErrorException, JsonProcessingException { - // create two accounts, one will be the destination in the tx - KeyPair senderAccount = constructRandomAccount(); + // create the destination account... KeyPair receiverAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. - // get account info for the sequence number + // get receiver account info for the sequence number AccountInfoResult receiverAccountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress()) ); @@ -195,45 +234,53 @@ void testAccountDeleteItFailsWith_NoPermission() throws JsonRpcClientErrorExcept // create, sign & submit DEPOSIT_AUTH AccountSet tx for receiver FeeResult feeResult = xrplClient.fee(); AccountSet accountSet = AccountSet.builder() - .account(receiverAccount.publicKey().deriveAddress()) - .fee(feeResult.drops().openLedgerFee()) - .sequence(receiverAccountInfo.accountData().sequence()) - .setFlag(AccountSet.AccountSetFlag.DEPOSIT_AUTH) - .signingPublicKey(receiverAccount.publicKey()) - .build(); + .account(receiverAccount.publicKey().deriveAddress()) + .fee(feeResult.drops().openLedgerFee()) + .sequence(receiverAccountInfo.accountData().sequence()) + .setFlag(AccountSet.AccountSetFlag.DEPOSIT_AUTH) + .signingPublicKey(receiverAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountSet = signatureService.sign( - receiverAccount.privateKey(), accountSet + receiverAccount.privateKey(), accountSet ); SubmitResult accountSetSubmitResult = xrplClient.submit(signedAccountSet); - assertThat(accountSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); assertThat(signedAccountSet.hash()).isEqualTo(accountSetSubmitResult.transactionResult().hash()); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. // confirm flag was set TransactionResult accountSetTransactionResult = this.scanForResult( () -> this.getValidatedTransaction(signedAccountSet.hash(), AccountSet.class) ); + assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null)) + .isEqualTo(AccountSet.AccountSetFlag.DEPOSIT_AUTH); AccountInfoResult updatedReceiverAccountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(receiverAccount.publicKey().deriveAddress()) ); - - assertThat(accountSetTransactionResult.transaction().setFlag().orElse(null)) - .isEqualTo(AccountSet.AccountSetFlag.DEPOSIT_AUTH); assertThat(updatedReceiverAccountInfo.accountData().flags().lsfDepositAuth()).isTrue(); + // Create the source account + KeyPair senderAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); + + // get sender account info for the sequence number + AccountInfoResult senderAccountInfo = this.scanForResult( + () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) + ); + // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(receiverAccountInfo.accountData().sequence()) - .destination(receiverAccount.publicKey().deriveAddress()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(senderAccountInfo.accountData().sequence()) + .destination(receiverAccount.publicKey().deriveAddress()) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -246,24 +293,25 @@ void testAccountDeleteItFailsWith_NoPermission() throws JsonRpcClientErrorExcept void testAccountDeleteItFailsWith_NoDestination() throws JsonRpcClientErrorException, JsonProcessingException { // create one account and a random key pair that will be used for the destination KeyPair senderAccount = constructRandomAccount(); - KeyPair randomKeyPair = Seed.ed25519Seed().deriveKeyPair(); + KeyPair randomDestinationKeyPair = Seed.ed25519Seed().deriveKeyPair(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. - // get account info for the sequence number - AccountInfoResult accountInfo = this.scanForResult( + // get sender account info for the sequence number + AccountInfoResult senderAccountInfoResult = this.scanForResult( () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) ); // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(accountInfo.accountData().sequence()) - .destination(randomKeyPair.publicKey().deriveAddress()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(senderAccountInfoResult.accountData().sequence()) + .destination(randomDestinationKeyPair.publicKey().deriveAddress()) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -276,26 +324,27 @@ void testAccountDeleteItFailsWith_NoDestination() throws JsonRpcClientErrorExcep void testAccountDeleteItFailsWith_HasObligations() throws JsonRpcClientErrorException, JsonProcessingException { // create sender account KeyPair senderAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. // get account info for the sequence number AccountInfoResult accountInfo = this.scanForResult( - () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) + () -> this.getValidatedAccountInfo(senderAccount.publicKey().deriveAddress()) ); // create EscrowCreate tx to link an account with an object for tecHAS_OBLIGATIONS error EscrowCreate escrowCreate = EscrowCreate.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(200)).build()) - .amount(XrpCurrencyAmount.of(UnsignedLong.valueOf(10))) - .sequence(accountInfo.accountData().sequence()) - .destination(senderAccount.publicKey().deriveAddress()) - .finishAfter(instantToXrpTimestamp(getMinExpirationTime().plus(Duration.ofSeconds(10)))) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(200)).build()) + .amount(XrpCurrencyAmount.of(UnsignedLong.valueOf(10))) + .sequence(accountInfo.accountData().sequence()) + .destination(senderAccount.publicKey().deriveAddress()) + .finishAfter(instantToXrpTimestamp(getMinExpirationTime().plus(Duration.ofSeconds(10)))) + .signingPublicKey(senderAccount.publicKey()) + .build(); // sign and submit EscrowCreate tx SingleSignedTransaction signedEscrowCreate = signatureService.sign( - senderAccount.privateKey(), escrowCreate + senderAccount.privateKey(), escrowCreate ); SubmitResult escrowCreateResult = xrplClient.submit(signedEscrowCreate); @@ -304,28 +353,31 @@ void testAccountDeleteItFailsWith_HasObligations() throws JsonRpcClientErrorExce // accept next 256 ledgers to avoid tec_TOOSOON error case and get current ledger index for (int i = 0; i < 256; i++) { - LocalRippledEnvironment localRippledEnvironment = - (LocalRippledEnvironment) XrplEnvironment.getConfiguredEnvironment(); + LocalRippledEnvironment localRippledEnvironment = (LocalRippledEnvironment) xrplEnvironment; localRippledEnvironment.acceptLedger(); } - LedgerResult lastLedgerResult = xrplClient.ledger(LedgerRequestParams.builder() - .ledgerSpecifier(LedgerSpecifier.CURRENT).build()); + LedgerResult lastLedgerResult = xrplClient.ledger( + LedgerRequestParams.builder() + .ledgerSpecifier(LedgerSpecifier.CURRENT).build() + ); // create receiver account, then create sign & submit AccountDelete tx KeyPair receiverAccount = constructRandomAccount(); AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(signedEscrowCreate.signedTransaction().sequence().plus(UnsignedInteger.ONE)) - .destination(receiverAccount.publicKey().deriveAddress()) - .lastLedgerSequence(lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(signedEscrowCreate.signedTransaction().sequence().plus(UnsignedInteger.ONE)) + .destination(receiverAccount.publicKey().deriveAddress()) + .lastLedgerSequence( + lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); SubmitResult response = xrplClient.submit(signedAccountDelete); @@ -340,6 +392,7 @@ void testAccountDeleteIt() throws JsonRpcClientErrorException, JsonProcessingExc // create two accounts, one will be the destination in the tx KeyPair senderAccount = constructRandomAccount(); KeyPair receiverAccount = constructRandomAccount(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. // get account info for the sequence number AccountInfoResult accountInfo = this.scanForResult( @@ -348,26 +401,27 @@ void testAccountDeleteIt() throws JsonRpcClientErrorException, JsonProcessingExc // accept next 256 ledgers to avoid tec_TOOSOON error case and get current ledger index for (int i = 0; i < 256; i++) { - LocalRippledEnvironment localRippledEnvironment = - (LocalRippledEnvironment) XrplEnvironment.getConfiguredEnvironment(); + LocalRippledEnvironment localRippledEnvironment = (LocalRippledEnvironment) xrplEnvironment; localRippledEnvironment.acceptLedger(); } LedgerResult lastLedgerResult = xrplClient.ledger(LedgerRequestParams.builder() - .ledgerSpecifier(LedgerSpecifier.CURRENT).build()); + .ledgerSpecifier(LedgerSpecifier.CURRENT).build()); // create, sign & submit AccountDelete tx AccountDelete accountDelete = AccountDelete.builder() - .account(senderAccount.publicKey().deriveAddress()) - .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) - .sequence(accountInfo.accountData().sequence()) - .destination(receiverAccount.publicKey().deriveAddress()) - .lastLedgerSequence(lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue()) - .signingPublicKey(senderAccount.publicKey()) - .build(); + .account(senderAccount.publicKey().deriveAddress()) + .fee(XrpCurrencyAmount.builder().value(UnsignedLong.valueOf(2000000)).build()) + .sequence(accountInfo.accountData().sequence()) + .destination(receiverAccount.publicKey().deriveAddress()) + .lastLedgerSequence( + lastLedgerResult.ledgerCurrentIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) + .signingPublicKey(senderAccount.publicKey()) + .build(); SingleSignedTransaction signedAccountDelete = signatureService.sign( - senderAccount.privateKey(), accountDelete + senderAccount.privateKey(), accountDelete ); // after 256 other txs are submitted, then submit AccountDelete diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountTransactionsIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountTransactionsIT.java index 6a17cc734..e23b634dc 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountTransactionsIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AccountTransactionsIT.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,6 +29,7 @@ import org.awaitility.Durations; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; +import org.junit.jupiter.api.condition.EnabledIf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; @@ -42,14 +43,27 @@ import org.xrpl.xrpl4j.model.client.ledger.LedgerResult; import org.xrpl.xrpl4j.model.transactions.Address; import org.xrpl.xrpl4j.model.transactions.Hash256; -import org.xrpl.xrpl4j.tests.environment.ReportingMainnetEnvironment; import org.xrpl.xrpl4j.tests.environment.XrplEnvironment; /** * An Integration Test to validate submission of Account transactions. */ +@EnabledIf(value = "shouldRun", disabledReason = "AccountTransactionsIT only runs when Clio Testnet is specified.") public class AccountTransactionsIT { + /** + * This test actually hits the production network, so we ordinarily don't want to run this test on every development + * run or even CI run. Instead, this test only executes in CI, and only when the suite is pointed at Clio. Note that + * Clio is chosen somewhat arbitrarily as the goal is to simply execute this test once per CI run. We could later + * adjust this to only run when the environment is chosen to be Mainnet, but this project doesn't yet support that as + * an Environment. + * + * @return {@code true} if clio network is the target execution environment; {@code false} otherwise. + */ + private static boolean shouldRun() { + return System.getProperty("useClioTestnet") != null; + } + private final Logger logger = LoggerFactory.getLogger(this.getClass()); // an arbitrary address on xrpl mainnet that has a decent amount of transaction history @@ -67,8 +81,8 @@ public void listTransactionsDefaultWithPagination() throws JsonRpcClientErrorExc } /** - * This test will fail if we hit a Clio node until CLIO-687 is deployed - * to mainnet. We expect 748 transactions across all pages of account_tx, however Clio duplicates the last + * This test will fail if we hit a Clio node until CLIO-687 is + * deployed to mainnet. We expect 748 transactions across all pages of account_tx, however Clio duplicates the last * transaction from each page in the next page, which results in more than 748 transactions returned. * * @throws JsonRpcClientErrorException If rippled/Clio returns an error or a request fails. diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java index 8a6799491..2c436e658 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/AmmIT.java @@ -90,7 +90,7 @@ void depositAndVoteOnTradingFee() throws JsonRpcClientErrorException, JsonProces .account(traderAccount.accountData().account()) .sequence(traderAccountAfterDeposit.accountData().sequence()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) - .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(8)).unsignedIntegerValue()) + .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .signingPublicKey(traderKeyPair.publicKey()) .asset2( Issue.builder() @@ -177,7 +177,7 @@ void depositAndBid() throws JsonRpcClientErrorException, JsonProcessingException .account(traderAccount.accountData().account()) .sequence(traderAccountAfterDeposit.accountData().sequence()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) - .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(8)).unsignedIntegerValue()) + .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .signingPublicKey(traderKeyPair.publicKey()) .asset2( Issue.builder() @@ -245,7 +245,7 @@ void depositAndWithdraw() throws JsonRpcClientErrorException, JsonProcessingExce .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(traderAccountAfterDeposit.accountData().sequence()) .lastLedgerSequence( - traderAccountAfterDeposit.ledgerCurrentIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue() + traderAccountAfterDeposit.ledgerCurrentIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue() ) .signingPublicKey(traderKeyPair.publicKey()) .asset2( @@ -311,7 +311,7 @@ private AccountInfoResult depositXrp( .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(traderAccount.accountData().sequence()) .signingPublicKey(traderKeyPair.publicKey()) - .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(traderAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .build(); SingleSignedTransaction signedDeposit = signatureService.sign(traderKeyPair.privateKey(), deposit); @@ -378,7 +378,7 @@ private AmmInfoResult createAmm( ) .amount2(XrpCurrencyAmount.ofXrp(BigDecimal.valueOf(10))) .tradingFee(TradingFee.ofPercent(BigDecimal.ONE)) - .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(issuerAccount.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .signingPublicKey(issuerKeyPair.publicKey()) .build(); @@ -417,7 +417,7 @@ private AmmInfoResult getAmmInfo(KeyPair issuerKeyPair) throws JsonRpcClientErro AmmInfoRequestParams.from(ammAccountInfo.accountData().account()) ); - assertThat(ammInfoByAccount).isEqualTo(ammInfoResult); + assertThat(ammInfoByAccount.amm()).isEqualTo(ammInfoResult.amm()); LedgerEntryResult ammObject = xrplClient.ledgerEntry( LedgerEntryRequestParams.amm( diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java index fcd886e17..e2e72adad 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/DidIT.java @@ -37,15 +37,8 @@ import java.util.List; import java.util.stream.Collectors; -@DisabledIf(value = "shouldRun", disabledReason = "DidIT only runs with local rippled nodes.") public class DidIT extends AbstractIT { - static boolean shouldRun() { - return System.getProperty("useTestnet") != null || - System.getProperty("useDevnet") != null || - System.getProperty("useClioTestnet") != null; - } - @Test void testCreateAndUpdateDid() throws JsonRpcClientErrorException, JsonProcessingException { TestDid did = createNewDid(); diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IsFinalIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IsFinalIT.java index da99a43fe..7de1934a5 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IsFinalIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/IsFinalIT.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,8 +25,11 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; import org.xrpl.xrpl4j.crypto.keys.KeyPair; import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction; @@ -45,17 +48,57 @@ import org.xrpl.xrpl4j.model.transactions.Payment; import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; +import java.time.Duration; + +@EnabledIf(value = "shouldRun", disabledReason = "IsFinalIT only runs runs with local rippled nodes.") public class IsFinalIT extends AbstractIT { - KeyPair keyPair = createRandomAccountEd25519(); - Address sourceAddress = keyPair.publicKey().deriveAddress(); + /** + * If any "real" testnet is being used (i.e., the environment specified is not a local one) then this test should not + * be run. + * + * @return {@code true} if test/dev/clio networks are the execution environment; {@code false} otherwise. + */ + private static boolean shouldRun() { + return System.getProperty("useTestnet") == null && + System.getProperty("useDevnet") == null && + System.getProperty("useClioTestnet") == null; + } + + private KeyPair keyPair; + private Address sourceAddress; ImmutablePayment.Builder payment; UnsignedInteger lastLedgerSequence; AccountInfoResult accountInfo; + /** + * This test requires the Ledger Acceptor to be disabled, in order to tightly control advancement of ledgers. Because + * of this, some of the tests do not execute when running against real networks (because controlling ledger + * advancement is not possible). + */ + @BeforeAll + static void setupTest() { + // Turn the LedgerAcceptor off + xrplEnvironment.stopLedgerAcceptor(); + } + + /** + * Because this test requires the Ledger Acceptor to be disabled, once the test completes, the Ledger Acceptor must be + * enabled again so that follow-on tests execute as expected. + */ + @AfterAll + static void cleanupTest() { + // Turn the LedgerAcceptor off + xrplEnvironment.startLedgerAcceptor(POLL_INTERVAL); + } + @BeforeEach void setup() throws JsonRpcClientErrorException { + keyPair = createRandomAccountEd25519(); + sourceAddress = keyPair.publicKey().deriveAddress(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. + /////////////////////// // Get validated account info and validate account state accountInfo = this.scanForResult(() -> this.getValidatedAccountInfo(sourceAddress)); @@ -65,11 +108,8 @@ void setup() throws JsonRpcClientErrorException { FeeResult feeResult = xrplClient.fee(); LedgerIndex validatedLedger = xrplClient.ledger( - LedgerRequestParams.builder().ledgerSpecifier(LedgerSpecifier.VALIDATED) - .build() - ) - .ledgerIndexSafe(); - + LedgerRequestParams.builder().ledgerSpecifier(LedgerSpecifier.VALIDATED).build() + ).ledgerIndexSafe(); lastLedgerSequence = validatedLedger.plus(UnsignedInteger.ONE).unsignedIntegerValue(); KeyPair destinationKeyPair = createRandomAccountEd25519(); @@ -80,16 +120,19 @@ void setup() throws JsonRpcClientErrorException { .destination(destinationKeyPair.publicKey().deriveAddress()) .amount(XrpCurrencyAmount.ofDrops(10)) .signingPublicKey(keyPair.publicKey()); + + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. } @Test - public void simpleIsFinalTest() throws JsonRpcClientErrorException, JsonProcessingException { + public void simpleIsFinalTest() throws JsonRpcClientErrorException, JsonProcessingException, InterruptedException { Payment builtPayment = payment.build(); SingleSignedTransaction signedPayment = signatureService.sign(keyPair.privateKey(), builtPayment); + + // Submit TX SubmitResult response = xrplClient.submit(signedPayment); assertThat(response.engineResult()).isEqualTo("tesSUCCESS"); Hash256 txHash = response.transactionResult().hash(); - assertThat( xrplClient.isFinal( txHash, @@ -100,6 +143,8 @@ public void simpleIsFinalTest() throws JsonRpcClientErrorException, JsonProcessi ).finalityStatus() ).isEqualTo(FinalityStatus.NOT_FINAL); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. + this.scanForResult( () -> getValidatedTransaction(txHash, Payment.class) ); @@ -117,7 +162,6 @@ public void simpleIsFinalTest() throws JsonRpcClientErrorException, JsonProcessi @Test public void isFinalExpiredTxTest() throws JsonRpcClientErrorException, JsonProcessingException { - Payment builtPayment = payment .sequence(accountInfo.accountData().sequence().minus(UnsignedInteger.ONE)) .build(); @@ -133,7 +177,9 @@ public void isFinalExpiredTxTest() throws JsonRpcClientErrorException, JsonProce accountInfo.accountData().sequence(), sourceAddress ).finalityStatus() - ).isEqualTo(FinalityStatus.NOT_FINAL); + ).isEqualTo(FinalityStatus.EXPIRED); + + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. this.scanForResult( () -> xrplClient.isFinal( @@ -150,10 +196,12 @@ public void isFinalExpiredTxTest() throws JsonRpcClientErrorException, JsonProce @Test public void isFinalNoTrustlineIouPayment_ValidatedFailureResponse() throws JsonRpcClientErrorException, JsonProcessingException { - Payment builtPayment = payment - .amount(IssuedCurrencyAmount.builder().currency("USD").issuer( - sourceAddress).value("500").build() + .amount(IssuedCurrencyAmount.builder() + .currency("USD") + .issuer(sourceAddress) + .value("500") + .build() ).build(); SingleSignedTransaction signedPayment = signatureService.sign(keyPair.privateKey(), builtPayment); SubmitResult response = xrplClient.submit(signedPayment); @@ -169,11 +217,14 @@ public void isFinalNoTrustlineIouPayment_ValidatedFailureResponse() ).finalityStatus() ).isEqualTo(FinalityStatus.NOT_FINAL); + // Accept the ledger to finalize the transaction... + xrplEnvironment.acceptLedger(); + this.scanForResult( () -> xrplClient.isFinal( response.transactionResult().hash(), response.validatedLedgerIndex(), - lastLedgerSequence.minus(UnsignedInteger.ONE), + response.transactionResult().transaction().sequence().minus(UnsignedInteger.ONE), accountInfo.accountData().sequence(), sourceAddress ).finalityStatus(), diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/OfferIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/OfferIT.java index 7cc13c93c..e1d693906 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/OfferIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/OfferIT.java @@ -394,7 +394,7 @@ private void assertThatEntryEqualsObjectFromAccountObjects(OfferObject offerObje */ private void assertEmptyResults(Supplier> supplier) { Awaitility.await() - .atMost(Durations.TEN_SECONDS) + .atMost(AT_MOST_INTERVAL) .until(supplier::get, Matchers.empty()); } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PaymentChannelIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PaymentChannelIT.java index 6ddfc914a..adc6418b6 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PaymentChannelIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PaymentChannelIT.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -26,6 +26,7 @@ import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; import org.xrpl.xrpl4j.crypto.keys.KeyPair; @@ -150,8 +151,7 @@ void createAndClaimPaymentChannel() throws JsonRpcClientErrorException, JsonProc ); ////////////////////////// - // Submit a PaymentChannelCreate transaction to create a payment channel between - // the source and destination accounts + // Submit a PaymentChannelCreate transaction to create a payment channel between the source and destination accounts PaymentChannelCreate paymentChannelCreate = PaymentChannelCreate.builder() .account(sourceKeyPair.publicKey().deriveAddress()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) @@ -160,7 +160,7 @@ void createAndClaimPaymentChannel() throws JsonRpcClientErrorException, JsonProc .destination(destinationKeyPair.publicKey().deriveAddress()) .settleDelay(UnsignedInteger.ONE) .publicKey(sourceKeyPair.publicKey().base16Value()) - .cancelAfter(this.instantToXrpTimestamp(Instant.now().plus(Duration.ofMinutes(1)))) + //.cancelAfter(this.instantToXrpTimestamp(Instant.now().plus(Duration.ofMinutes(1)))) .signingPublicKey(sourceKeyPair.publicKey()) .build(); @@ -192,7 +192,7 @@ void createAndClaimPaymentChannel() throws JsonRpcClientErrorException, JsonProc assertThat(paymentChannel.amount()).isEqualTo(paymentChannelCreate.amount()); assertThat(paymentChannel.settleDelay()).isEqualTo(paymentChannelCreate.settleDelay()); assertThat(paymentChannel.publicKeyHex()).isNotEmpty().get().isEqualTo(paymentChannelCreate.publicKey()); - assertThat(paymentChannel.cancelAfter()).isNotEmpty().get().isEqualTo(paymentChannelCreate.cancelAfter().get()); + assertThat(paymentChannel.cancelAfter()).isEmpty(); PayChannelObject payChannelObject = scanForPayChannelObject(sourceKeyPair, destinationKeyPair); assertThatEntryEqualsObjectFromAccountObjects(payChannelObject); @@ -280,11 +280,11 @@ void createAddFundsAndSetExpirationToPaymentChannel() throws JsonRpcClientErrorE .account(sourceKeyPair.publicKey().deriveAddress()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(senderAccountInfo.accountData().sequence()) - .amount(XrpCurrencyAmount.ofDrops(10000000)) + .amount(XrpCurrencyAmount.ofDrops(10_000_000)) // <-- 10 XRP or 10m drops .destination(destinationKeyPair.publicKey().deriveAddress()) .settleDelay(UnsignedInteger.ONE) .publicKey(sourceKeyPair.publicKey().base16Value()) - .cancelAfter(this.instantToXrpTimestamp(Instant.now().plus(Duration.ofMinutes(1)))) + .cancelAfter(this.instantToXrpTimestamp(Instant.now().plus(Duration.ofMinutes(60)))) .signingPublicKey(sourceKeyPair.publicKey()) .build(); @@ -301,8 +301,7 @@ void createAddFundsAndSetExpirationToPaymentChannel() throws JsonRpcClientErrorE ); ////////////////////////// - // Wait for the payment channel to exist in a validated ledger - // and validate its fields + // Wait for the payment channel to exist in a validated ledger and validate its fields PaymentChannelResultObject paymentChannel = scanForResult( () -> getValidatedAccountChannels(sourceKeyPair.publicKey().deriveAddress()), channels -> channels.channels().stream() @@ -327,7 +326,7 @@ void createAddFundsAndSetExpirationToPaymentChannel() throws JsonRpcClientErrorE .sequence(senderAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) .signingPublicKey(sourceKeyPair.publicKey()) .channel(paymentChannel.channelId()) - .amount(XrpCurrencyAmount.ofDrops(10000)) + .amount(XrpCurrencyAmount.ofDrops(10_000)) // <-- 10k drops .build(); ////////////////////////// @@ -347,10 +346,12 @@ void createAddFundsAndSetExpirationToPaymentChannel() throws JsonRpcClientErrorE scanForResult( () -> getValidatedAccountChannels(sourceKeyPair.publicKey().deriveAddress()), channelsResult -> channelsResult.channels().stream() - .anyMatch( - channel -> - channel.channelId().equals(paymentChannel.channelId()) && - channel.amount().equals(paymentChannel.amount().plus(paymentChannelFund.amount())) + .anyMatch(channel -> { + logger.warn("PAYCHAN: channel={} paymentChannel={}", channel, paymentChannel); + + return channel.channelId().equals(paymentChannel.channelId()) && + channel.amount().equals(paymentChannel.amount().plus(paymentChannelFund.amount())); + } ) ); @@ -362,7 +363,7 @@ void createAddFundsAndSetExpirationToPaymentChannel() throws JsonRpcClientErrorE // transaction with an expiration and 1 drop of XRP in the amount field UnsignedLong newExpiry = instantToXrpTimestamp(Instant.now()) .plus(UnsignedLong.valueOf(paymentChannel.settleDelay().longValue())) - .plus(UnsignedLong.valueOf(30)); + .plus(UnsignedLong.valueOf(300000)); PaymentChannelFund paymentChannelFundWithNewExpiry = PaymentChannelFund.builder() .account(sourceKeyPair.publicKey().deriveAddress()) diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PriceOracleIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PriceOracleIT.java index 5987f8470..5bd056fba 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PriceOracleIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/PriceOracleIT.java @@ -9,9 +9,12 @@ import com.google.common.io.BaseEncoding; import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedLong; -import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIf; +import org.junit.jupiter.api.condition.EnabledIf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.xrpl.xrpl4j.client.JsonRpcClientErrorException; import org.xrpl.xrpl4j.crypto.keys.KeyPair; import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction; @@ -23,6 +26,7 @@ import org.xrpl.xrpl4j.model.client.fees.FeeUtils; import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryRequestParams; import org.xrpl.xrpl4j.model.client.ledger.LedgerEntryResult; +import org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams; import org.xrpl.xrpl4j.model.client.ledger.OracleLedgerEntryParams; import org.xrpl.xrpl4j.model.client.oracle.GetAggregatePriceRequestParams; import org.xrpl.xrpl4j.model.client.oracle.GetAggregatePriceResult; @@ -38,31 +42,70 @@ import org.xrpl.xrpl4j.model.transactions.PriceDataWrapper; import java.math.BigDecimal; -import java.time.Instant; +import java.util.Objects; import java.util.Optional; -@DisabledIf(value = "shouldNotRun", disabledReason = "PriceOracleIT only runs on local rippled node or devnet.") +@EnabledIf(value = "shouldRun", disabledReason = "PriceOracleIT only runs runs with local rippled nodes.") public class PriceOracleIT extends AbstractIT { - static boolean shouldNotRun() { - return System.getProperty("useTestnet") != null || - System.getProperty("useClioTestnet") != null; + private static final Logger LOGGER = LoggerFactory.getLogger(PriceOracleIT.class); + + /** + * If any "real" testnet is being used (i.e., the environment specified is not a local one) then this test should not + * be run. + * + * @return {@code true} if test/dev/clio networks are the execution environment; {@code false} otherwise. + */ + private static boolean shouldRun() { + return System.getProperty("useTestnet") == null && + System.getProperty("useDevnet") == null && + System.getProperty("useClioTestnet") == null; + } + + /** + * This test requires the Ledger Acceptor to be disabled, in order to tightly control advancement of ledgers. Because + * of this, some of the tests do not execute when running against real networks (because controlling ledger + * advancement is not possible). + */ + @BeforeAll + static void setupTest() { + // Turn the LedgerAcceptor off + LOGGER.info("########### STOPPING LEDGER ACCEPTOR #########"); + xrplEnvironment.stopLedgerAcceptor(); + LOGGER.info("########### LEDGER ACCEPTOR STOPPED #########"); + } + + /** + * Because this test requires the Ledger Acceptor to be disabled, once the test completes, the Ledger Acceptor must be + * enabled again so that follow-on tests execute as expected. + */ + @AfterAll + static void cleanupTest() { + // Turn the LedgerAcceptor off + LOGGER.info("########### STARTING LEDGER ACCEPTOR #########"); + xrplEnvironment.startLedgerAcceptor(POLL_INTERVAL); + LOGGER.info("########### LEDGER ACCEPTOR STARTED #########"); + } - String xrpl4jCoin = Strings.padEnd(BaseEncoding.base16().encode("xrpl4jCoin".getBytes()), 40, '0'); + private static final String xrpl4jCoin = + Strings.padEnd(BaseEncoding.base16().encode("xrpl4jCoin".getBytes()), 40, '0'); @Test void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonProcessingException { + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. KeyPair sourceKeyPair = createRandomAccountEd25519(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. AccountInfoResult accountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(sourceKeyPair.publicKey().deriveAddress()) ); FeeResult feeResult = xrplClient.fee(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. + OracleProvider provider = OracleProvider.of(BaseEncoding.base16().encode("DIA".getBytes())); - OracleUri uri = OracleUri.of(BaseEncoding.base16().encode("http://example.com".getBytes())); - UnsignedInteger lastUpdateTime = unixTimestamp(); + OracleUri uri = OracleUri.of(BaseEncoding.base16().encode("https://example.com".getBytes())); String assetClass = BaseEncoding.base16().encode("currency".getBytes()); PriceDataWrapper priceData1 = PriceDataWrapper.of( PriceData.builder() @@ -80,12 +123,15 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr .scale(UnsignedInteger.valueOf(10)) .build() ); + + UnsignedInteger lastUpdateTime = closeTimeHuman(); + OracleSet oracleSet = OracleSet.builder() .account(sourceKeyPair.publicKey().deriveAddress()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(accountInfo.accountData().sequence()) .signingPublicKey(sourceKeyPair.publicKey()) - .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .oracleDocumentId(OracleDocumentId.of(UnsignedInteger.ONE)) .provider(provider) .uri(uri) @@ -97,11 +143,12 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr SingleSignedTransaction signedOracleSet = signatureService.sign(sourceKeyPair.privateKey(), oracleSet); SubmitResult oracleSetSubmitResult = xrplClient.submit(signedOracleSet); assertThat(oracleSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. Finality finality = scanForFinality( signedOracleSet.hash(), accountInfo.ledgerIndexSafe(), - oracleSet.lastLedgerSequence().get(), + oracleSet.lastLedgerSequence().orElseThrow(RuntimeException::new), oracleSet.sequence(), sourceKeyPair.publicKey().deriveAddress() ); @@ -124,7 +171,6 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr assertThat(oracleObject.uri()).isNotEmpty().get().isEqualTo(uri); assertThat(oracleObject.priceDataSeries()).containsExactlyInAnyOrder(priceData1, priceData2); - UnsignedInteger lastUpdateTime2 = unixTimestamp(); PriceDataWrapper newPriceData = PriceDataWrapper.of( PriceData.builder() .baseAsset("XRP") @@ -138,9 +184,11 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr .assetPrice(AssetPrice.of(UnsignedLong.valueOf(1000))) .build() ); + UnsignedInteger moreRecentLastUpdateTime = closeTimeHuman(); + OracleSet oracleUpdate = OracleSet.builder().from(oracleSet) - .lastLedgerSequence(ledgerEntry.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) - .lastUpdateTime(lastUpdateTime2) + .lastLedgerSequence(ledgerEntry.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) + .lastUpdateTime(moreRecentLastUpdateTime) .sequence(oracleSet.sequence().plus(UnsignedInteger.ONE)) .priceDataSeries(Lists.newArrayList( // New asset pair should get added @@ -162,11 +210,12 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr ); SubmitResult oracleUpdateSubmitResult = xrplClient.submit(signedOracleUpdate); assertThat(oracleUpdateSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. Finality updateFinality = scanForFinality( signedOracleUpdate.hash(), accountInfo.ledgerIndexSafe(), - oracleUpdate.lastLedgerSequence().get(), + oracleUpdate.lastLedgerSequence().orElseThrow(RuntimeException::new), oracleUpdate.sequence(), sourceKeyPair.publicKey().deriveAddress() ); @@ -185,7 +234,7 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr assertThat(oracleObject.owner()).isEqualTo(sourceKeyPair.publicKey().deriveAddress()); assertThat(oracleObject.provider()).isEqualTo(provider); assertThat(oracleObject.assetClass()).isEqualTo(assetClass); - assertThat(oracleObject.lastUpdateTime()).isEqualTo(lastUpdateTime2); + assertThat(oracleObject.lastUpdateTime()).isEqualTo(moreRecentLastUpdateTime); assertThat(oracleObject.uri()).isNotEmpty().get().isEqualTo(uri); assertThat(oracleObject.priceDataSeries()).containsExactlyInAnyOrder(newPriceData, updatedPriceData); @@ -193,7 +242,7 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr .account(sourceKeyPair.publicKey().deriveAddress()) .fee(oracleSet.fee()) .sequence(oracleUpdate.sequence().plus(UnsignedInteger.ONE)) - .lastLedgerSequence(ledgerEntry.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(ledgerEntry.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .signingPublicKey(sourceKeyPair.publicKey()) .oracleDocumentId(oracleSet.oracleDocumentId()) .build(); @@ -202,11 +251,12 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr ); SubmitResult oracleDeleteSubmitResult = xrplClient.submit(signedOracleDelete); assertThat(oracleDeleteSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. Finality deleteFinality = scanForFinality( signedOracleDelete.hash(), accountInfo.ledgerIndexSafe(), - oracleDelete.lastLedgerSequence().get(), + oracleDelete.lastLedgerSequence().orElseThrow(RuntimeException::new), oracleDelete.sequence(), sourceKeyPair.publicKey().deriveAddress() ); @@ -220,13 +270,13 @@ void createAndUpdateAndDeleteOracle() throws JsonRpcClientErrorException, JsonPr .build(), LedgerSpecifier.VALIDATED ) - )).isInstanceOf(JsonRpcClientErrorException.class) - .hasMessage("entryNotFound (n/a)"); + )).isInstanceOf(JsonRpcClientErrorException.class).hasMessage("entryNotFound (n/a)"); } @Test void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, JsonProcessingException { KeyPair sourceKeyPair = createRandomAccountEd25519(); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. AccountInfoResult accountInfo = this.scanForResult( () -> this.getValidatedAccountInfo(sourceKeyPair.publicKey().deriveAddress()) @@ -234,8 +284,7 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, FeeResult feeResult = xrplClient.fee(); OracleProvider provider = OracleProvider.of(BaseEncoding.base16().encode("DIA".getBytes())); - OracleUri uri = OracleUri.of(BaseEncoding.base16().encode("http://example.com".getBytes())); - UnsignedInteger lastUpdateTime = unixTimestamp(); + OracleUri uri = OracleUri.of(BaseEncoding.base16().encode("https://example.com".getBytes())); String assetClass = BaseEncoding.base16().encode("currency".getBytes()); PriceDataWrapper priceData1 = PriceDataWrapper.of( PriceData.builder() @@ -244,12 +293,14 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, .assetPrice(AssetPrice.of(UnsignedLong.ONE)) .build() ); + UnsignedInteger lastUpdateTime = closeTimeHuman(); + OracleSet oracleSet = OracleSet.builder() .account(sourceKeyPair.publicKey().deriveAddress()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(accountInfo.accountData().sequence()) .signingPublicKey(sourceKeyPair.publicKey()) - .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .oracleDocumentId(OracleDocumentId.of(UnsignedInteger.ONE)) .provider(provider) .uri(uri) @@ -261,11 +312,12 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, SingleSignedTransaction signedOracleSet = signatureService.sign(sourceKeyPair.privateKey(), oracleSet); SubmitResult oracleSetSubmitResult = xrplClient.submit(signedOracleSet); assertThat(oracleSetSubmitResult.engineResult()).isEqualTo("tesSUCCESS"); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. Finality finality = scanForFinality( signedOracleSet.hash(), accountInfo.ledgerIndexSafe(), - oracleSet.lastLedgerSequence().get(), + oracleSet.lastLedgerSequence().orElseThrow(RuntimeException::new), oracleSet.sequence(), sourceKeyPair.publicKey().deriveAddress() ); @@ -278,16 +330,19 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, .assetPrice(AssetPrice.of(UnsignedLong.valueOf(2))) .build() ); + + UnsignedInteger lastUpdateTime2 = closeTimeHuman(); + OracleSet oracleSet2 = OracleSet.builder() .account(sourceKeyPair.publicKey().deriveAddress()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) .sequence(accountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) .signingPublicKey(sourceKeyPair.publicKey()) - .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(8)).unsignedIntegerValue()) + .lastLedgerSequence(accountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .oracleDocumentId(OracleDocumentId.of(UnsignedInteger.valueOf(2))) .provider(provider) .uri(uri) - .lastUpdateTime(lastUpdateTime) + .lastUpdateTime(lastUpdateTime2) .assetClass(assetClass) .addPriceDataSeries(priceData2) .build(); @@ -295,11 +350,12 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, SingleSignedTransaction signedOracleSet2 = signatureService.sign(sourceKeyPair.privateKey(), oracleSet2); SubmitResult oracleSetSubmitResult2 = xrplClient.submit(signedOracleSet2); assertThat(oracleSetSubmitResult2.engineResult()).isEqualTo("tesSUCCESS"); + xrplEnvironment.acceptLedger(); // <-- Progress the ledger to ensure the above tx becomes Validated. Finality finality2 = scanForFinality( signedOracleSet2.hash(), accountInfo.ledgerIndexSafe(), - oracleSet2.lastLedgerSequence().get(), + oracleSet2.lastLedgerSequence().orElseThrow(RuntimeException::new), oracleSet2.sequence(), sourceKeyPair.publicKey().deriveAddress() ); @@ -330,7 +386,31 @@ void createTwoOraclesAndGetAggregatePrice() throws JsonRpcClientErrorException, assertThat(aggregatePrice.trimmedSet()).isNotEmpty().get().isEqualTo(aggregatePrice.entireSet()); } - private static UnsignedInteger unixTimestamp() { - return UnsignedInteger.valueOf(System.currentTimeMillis() / 1000L); + /** + * Get the ledger's view of clock-time by inspecting the `close_time_human` property on the most recently validated + * ledger (i.e., The time this ledger was closed, in human-readable format. Always uses the UTC time zone). + * + *

This value is used instead of the unix-time returned by the JVM because these two versions of time will diverge + * when ledgers are closed very quickly, as is the case with the default ledger acceptor polling interval (see + * {@link AbstractIT#POLL_INTERVAL}). + * + * @return An {@link UnsignedInteger} representing the seconds from the unix epoch of 1970-01-01T00:00:00Z. + * + * @see "https://xrpl.org/docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger" + */ + private UnsignedInteger closeTimeHuman() throws JsonRpcClientErrorException { + Objects.requireNonNull(xrplClient); + + final long closeTimeHuman = xrplClient.ledger( + LedgerRequestParams.builder().ledgerSpecifier(LedgerSpecifier.VALIDATED).build() + ) + .ledger().closeTimeHuman() + .orElseThrow(() -> new RuntimeException("closeTimeHuman must exist")) + .toInstant() + .getEpochSecond(); + + return UnsignedInteger.valueOf(closeTimeHuman); } + + } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java index e5012517c..fdb66407d 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/XChainIT.java @@ -111,7 +111,9 @@ void testXChainAccountCreateCommit() throws JsonRpcClientErrorException, JsonPro .account(sender.publicKey().deriveAddress()) .sequence(senderAccountInfo.accountData().sequence()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) - .lastLedgerSequence(senderAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence( + senderAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue() + ) .signingPublicKey(sender.publicKey()) .xChainBridge(testBridge.bridge()) .amount(amount) @@ -169,7 +171,9 @@ void testXChainAddAccountCreateAttestation() throws JsonProcessingException, Jso .attestationSignerAccount(testBridge.witnessKeyPair().publicKey().deriveAddress()) .sequence(sourceAccountInfo.accountData().sequence()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(source.publicKey()) .build(); @@ -267,7 +271,9 @@ void testXChainAddAccountCreateAttestation() throws JsonProcessingException, Jso .attestationSignerAccount(testBridge.witnessKeyPair2().publicKey().deriveAddress()) .sequence(sourceAccountInfo2.accountData().sequence()) .fee(FeeUtils.computeNetworkFees(feeResult).recommendedFee()) - .lastLedgerSequence(sourceAccountInfo2.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + sourceAccountInfo2.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(source.publicKey()) .build(); @@ -562,7 +568,7 @@ void testXChainClaim() throws JsonRpcClientErrorException, JsonProcessingExcepti .sequence(destinationAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) .fee(fee) .lastLedgerSequence( - destinationAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(10)) + destinationAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) ) .signingPublicKey(destination.publicKey()) .xChainBridge(testBridge.bridge()) @@ -601,7 +607,8 @@ void testXChainCommit() throws JsonRpcClientErrorException, JsonProcessingExcept .account(source.publicKey().deriveAddress()) .sequence(sourceAccountInfo.accountData().sequence()) .fee(fee) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000))) .signingPublicKey(source.publicKey()) .xChainBridge(testBridge.bridge()) .xChainClaimId(XChainClaimId.of(UnsignedLong.ONE)) @@ -641,7 +648,8 @@ void testXChainModifyBridge() throws JsonRpcClientErrorException, JsonProcessing .account(sourceAccountInfo.accountData().account()) .sequence(sourceAccountInfo.accountData().sequence()) .fee(fee) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000))) .signingPublicKey(source.publicKey()) .xChainBridge( XChainBridge.builder() @@ -678,7 +686,9 @@ void testXChainModifyBridge() throws JsonRpcClientErrorException, JsonProcessing .account(sourceAccountInfo.accountData().account()) .sequence(sourceAccountInfo.accountData().sequence().plus(UnsignedInteger.ONE)) .fee(fee) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(10))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(source.publicKey()) .xChainBridge(bridgeObject.xChainBridge()) .signatureReward(XrpCurrencyAmount.ofDrops(300)) @@ -706,7 +716,9 @@ void testXChainModifyBridge() throws JsonRpcClientErrorException, JsonProcessing .account(sourceAccountInfo.accountData().account()) .sequence(sourceAccountInfo.accountData().sequence().plus(UnsignedInteger.valueOf(2))) .fee(fee) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(14))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(source.publicKey()) .xChainBridge(bridgeObject.xChainBridge()) .flags(XChainModifyBridgeFlags.CLEAR_ACCOUNT_CREATE_AMOUNT) @@ -742,7 +754,9 @@ private XChainOwnedClaimIdObject createClaimId( .sequence(sourceAccountInfo.accountData().sequence()) .fee(fee) .signingPublicKey(source.publicKey()) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue() + ) .xChainBridge(bridge.bridge()) .signatureReward(bridge.signatureReward()) .otherChainSource(otherChainSource.publicKey().deriveAddress()) @@ -782,7 +796,9 @@ private void enableRippling(KeyPair source, XrpCurrencyAmount fee) .account(sourceAccountInfo.accountData().account()) .sequence(sourceAccountInfo.accountData().sequence()) .fee(fee) - .lastLedgerSequence(sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + sourceAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(source.publicKey()) .setFlag(AccountSetFlag.DEFAULT_RIPPLE) .build(); @@ -818,7 +834,9 @@ private TransactionResult addClaimAttestation( .account(witnessAccountInfo.accountData().account()) .sequence(witnessAccountInfo.accountData().sequence()) .fee(fee) - .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(witnessKeyPair.publicKey()) .xChainBridge(bridge) .otherChainSource(otherChainSource.publicKey().deriveAddress()) @@ -863,7 +881,9 @@ private TransactionResult addClaimAttestationForClaim .account(witnessAccountInfo.accountData().account()) .sequence(witnessAccountInfo.accountData().sequence()) .fee(fee) - .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4))) + .lastLedgerSequence( + witnessAccountInfo.ledgerIndexSafe().unsignedIntegerValue().plus(UnsignedInteger.valueOf(4000)) + ) .signingPublicKey(witnessKeyPair.publicKey()) .xChainBridge(bridge) .otherChainSource(otherChainSource.publicKey().deriveAddress()) @@ -900,7 +920,7 @@ private TestBridge setupBridge( .sequence(doorAccountInfo.accountData().sequence()) .fee(XrpCurrencyAmount.ofDrops(100)) .signingPublicKey(doorKeyPair.publicKey()) - .lastLedgerSequence(doorAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence(doorAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue()) .build(); signSubmitAndWait(createBridge, doorKeyPair, XChainCreateBridge.class); @@ -960,7 +980,9 @@ private TestBridge setupBridge( .build() ) ) - .lastLedgerSequence(witnessAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4)).unsignedIntegerValue()) + .lastLedgerSequence( + witnessAccountInfo.ledgerIndexSafe().plus(UnsignedInteger.valueOf(4000)).unsignedIntegerValue() + ) .build(); this.signSubmitAndWait(signerListSet, doorKeyPair, SignerListSet.class); diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/AbstractXrplEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/AbstractXrplEnvironment.java new file mode 100644 index 000000000..a3802fa54 --- /dev/null +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/AbstractXrplEnvironment.java @@ -0,0 +1,26 @@ +package org.xrpl.xrpl4j.tests.environment; + +import java.time.Duration; + +/** + * An abstract implementation of {@link XrplEnvironment}. Note that this abstract implementation disallows manually + * accepting a ledger using the `ledger_accept` method, so subclasses that wish to enable this functionality must + * override the default behavior defined in this class. + */ +public abstract class AbstractXrplEnvironment implements XrplEnvironment { + + @Override + public void acceptLedger() { + throw new UnsupportedOperationException("Manual `ledger_accept` calls are not allowed for this Environment."); + } + + @Override + public void startLedgerAcceptor(Duration acceptIntervalMillis) { + throw new UnsupportedOperationException("Manual `ledger_accept` calls are not allowed for this Environment."); + } + + @Override + public void stopLedgerAcceptor() { + throw new UnsupportedOperationException("Manual `ledger_accept` calls are not allowed for this Environment."); + } +} diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/ClioMainnetEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/ClioMainnetEnvironment.java index 1404d1e92..ac11306a7 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/ClioMainnetEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/ClioMainnetEnvironment.java @@ -36,5 +36,4 @@ public class ClioMainnetEnvironment extends MainnetEnvironment { public XrplClient getXrplClient() { return xrplClient; } - } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/CustomEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/CustomEnvironment.java index 6377bfe64..3206b9367 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/CustomEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/CustomEnvironment.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,17 +29,18 @@ import java.util.Objects; /** - * Create a custom environment to talk to custom rippled by providing the serverUrl. - * Also, has faucet support to fund accounts. + * Create a custom environment to talk to custom rippled by providing the serverUrl. Also, has faucet support to fund + * accounts. */ -public class CustomEnvironment implements XrplEnvironment { +public class CustomEnvironment extends AbstractXrplEnvironment implements XrplEnvironment { private final XrplClient xrplClient; private FaucetClient faucetClient; private HttpUrl faucetUrl; - /** Constructor to create custom environment. + /** + * Constructor to create custom environment. * * @param serverUrl to connect to a running XRPL server. * @param faucetUrl to fund the accounts on the server. diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/DevnetEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/DevnetEnvironment.java index 24bc4574f..13e5686dc 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/DevnetEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/DevnetEnvironment.java @@ -29,7 +29,7 @@ /** * XRPL devnet environment. */ -public class DevnetEnvironment implements XrplEnvironment { +public class DevnetEnvironment extends AbstractXrplEnvironment implements XrplEnvironment { private final FaucetClient faucetClient = FaucetClient.construct(HttpUrl.parse("https://faucet.devnet.rippletest.net")); @@ -46,4 +46,4 @@ public void fundAccount(Address classicAddress) { faucetClient.fundAccount(FundAccountRequest.of(classicAddress)); } -} \ No newline at end of file +} diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java index 71c0aa553..2c9e6f831 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/LocalRippledEnvironment.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -43,6 +43,8 @@ import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; import java.math.BigDecimal; +import java.time.Duration; +import java.util.Objects; /** * Environment that runs a local rippled inside docker in standalone mode. @@ -54,7 +56,7 @@ public class LocalRippledEnvironment implements XrplEnvironment { private static final RippledContainer rippledContainer = new RippledContainer().start(); private final SignatureService signatureService = new BcSignatureService(); - + @Override public XrplClient getXrplClient() { return rippledContainer.getXrplClient(); @@ -71,7 +73,7 @@ public void fundAccount(Address classicAddress) { ); } catch (JsonRpcClientErrorException | JsonProcessingException e) { throw new RuntimeException("could not fund account", e); - } + } } protected AccountInfoResult getCurrentAccountInfo(Address classicAddress) { @@ -106,7 +108,19 @@ protected void sendPayment(KeyPair sourceKeyPair, Address destinationAddress, Xr /** * Method to accept next ledger ad hoc, only available in RippledContainer.java implementation. */ + @Override public void acceptLedger() { rippledContainer.acceptLedger(); } + + @Override + public void startLedgerAcceptor(Duration acceptIntervalMillis) { + Objects.requireNonNull(acceptIntervalMillis); + rippledContainer.startLedgerAcceptor(acceptIntervalMillis); + } + + @Override + public void stopLedgerAcceptor() { + rippledContainer.stopLedgerAcceptor(); + } } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/MainnetEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/MainnetEnvironment.java index 09d5e7f2f..06732df42 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/MainnetEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/MainnetEnvironment.java @@ -26,7 +26,7 @@ /** * Abstract class representing integration test environment that uses Mainnet as its XRPL network. */ -public abstract class MainnetEnvironment implements XrplEnvironment { +public abstract class MainnetEnvironment extends AbstractXrplEnvironment implements XrplEnvironment { @Override public abstract XrplClient getXrplClient(); diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java index e123a1f48..737779ace 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/RippledContainer.java @@ -23,6 +23,7 @@ import static org.slf4j.LoggerFactory.getLogger; import com.github.dockerjava.api.command.CreateContainerCmd; +import com.google.common.base.Preconditions; import okhttp3.HttpUrl; import org.awaitility.Awaitility; import org.slf4j.Logger; @@ -35,15 +36,18 @@ import org.xrpl.xrpl4j.crypto.keys.Base58EncodedSecret; import org.xrpl.xrpl4j.crypto.keys.KeyPair; import org.xrpl.xrpl4j.crypto.keys.Seed; +import org.xrpl.xrpl4j.model.client.admin.AcceptLedgerResult; import org.xrpl.xrpl4j.model.client.serverinfo.ReportingModeServerInfo; import org.xrpl.xrpl4j.model.client.serverinfo.RippledServerInfo; import java.time.Duration; import java.time.Instant; import java.time.ZonedDateTime; +import java.util.Objects; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; /** @@ -54,30 +58,41 @@ public class RippledContainer { // Seed for the Master/Root wallet in the rippled docker container. public static final String MASTER_WALLET_SEED = "snoPBrXtMeMyMHUVTgbuqAfg1SUTb"; private static final Logger LOGGER = getLogger(RippledContainer.class); + private static ScheduledExecutorService ledgerAcceptor = null; + + /** + * Advances the ledger by one on each call. + * + * @see "https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/server-control-methods/ledger_accept" + */ private static final Consumer LEDGER_ACCEPTOR = (rippledContainer) -> { try { - rippledContainer.getXrplAdminClient().acceptLedger(); + AcceptLedgerResult status = rippledContainer.getXrplAdminClient().acceptLedger(); + LOGGER.info("LEDGER_ACCEPTOR: Accepted ledger status: {}", status); } catch (RuntimeException | JsonRpcClientErrorException e) { LOGGER.warn("Ledger accept failed", e); } }; + private final GenericContainer rippledContainer; - private final ScheduledExecutorService ledgerAcceptor; + private XrplAdminClient xrplAdminClient; private boolean started; /** * No-args constructor. */ public RippledContainer() { - rippledContainer = new GenericContainer<>("rippleci/rippled:2.2.0-rc3") - .withCreateContainerCmdModifier((Consumer) (cmd) -> - cmd.withEntrypoint("/opt/ripple/bin/rippled")) - .withCommand("-a --start --conf /config/rippled.cfg") - .withExposedPorts(5005) - .withClasspathResourceMapping("rippled", - "/config", - BindMode.READ_ONLY) - .waitingFor(new LogMessageWaitStrategy().withRegEx(".*Application starting.*")); + try (GenericContainer container = new GenericContainer<>("rippleci/rippled:2.2.0")) { + this.rippledContainer = container.withCreateContainerCmdModifier((Consumer) (cmd) -> + cmd.withEntrypoint("/opt/ripple/bin/rippled")) + .withCommand("-a --start --conf /config/rippled.cfg") + .withExposedPorts(5005) + .withClasspathResourceMapping("rippled", + "/config", + BindMode.READ_ONLY) + .waitingFor(new LogMessageWaitStrategy().withRegEx(".*Application starting.*")); + } + ledgerAcceptor = Executors.newScheduledThreadPool(1); } @@ -94,7 +109,7 @@ public static KeyPair getMasterKeyPair() { * Starts container with default interval (1s) for closing ledgers. */ public RippledContainer start() { - return this.start(1000); + return this.start(Duration.ofMillis(10)); } /** @@ -104,21 +119,20 @@ public RippledContainer start() { * * @return A {@link RippledContainer}. */ - public RippledContainer start(int acceptIntervalMillis) { + public RippledContainer start(final Duration acceptIntervalMillis) { + Objects.requireNonNull(ledgerAcceptor); + if (started) { throw new IllegalStateException("container already started"); } started = true; rippledContainer.start(); - // rippled is run in standalone mode which means that ledgers won't automatically close. You have to manually - // advance the ledger using the "ledger_accept" method on the admin API. To mimic the behavior of a networked - // rippled, run a scheduled task to trigger the "ledger_accept" method. - ledgerAcceptor.scheduleAtFixedRate(() -> LEDGER_ACCEPTOR.accept(this), - acceptIntervalMillis, - acceptIntervalMillis, - TimeUnit.MILLISECONDS); - waitForLedgerTimeToSync(); + // Re-used the same client for all admin requests + xrplAdminClient = new XrplAdminClient(this.getBaseUri()); + + this.startLedgerAcceptor(acceptIntervalMillis); + return this; } @@ -167,7 +181,7 @@ private void assertContainerStarted() { * @return A {@link XrplAdminClient}. */ public XrplAdminClient getXrplAdminClient() { - return new XrplAdminClient(this.getBaseUri()); + return this.xrplAdminClient; } /** @@ -189,10 +203,48 @@ public HttpUrl getBaseUri() { } /** - * Exposed method to accept next ledger ad hoc. + * Accept the next ledger on an ad-hoc basis. */ public void acceptLedger() { - assertContainerStarted(); + assertContainerStarted(); // <-- This shouldn't work if the rippled container has not yet started (or is shutdown) LEDGER_ACCEPTOR.accept(this); } + + /** + * Helper method to start the Ledger Acceptor on a regular interval. + * + * @param acceptIntervalMillis The interval, in milliseconds, between regular calls to the `ledger_accept` method. + * This method is responsible for accepting new transactions into the ledger. + * + * @see "https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/server-control-methods/ledger_accept" + */ + public void startLedgerAcceptor(final Duration acceptIntervalMillis) { + Objects.requireNonNull(acceptIntervalMillis, "acceptIntervalMillis must not be null"); + Preconditions.checkArgument(acceptIntervalMillis.toMillis() > 0, "acceptIntervalMillis must be greater than 0"); + + // rippled is run in standalone mode which means that ledgers won't automatically close. You have to manually + // advance the ledger using the "ledger_accept" method on the admin API. To mimic the behavior of a networked + // rippled, run a scheduled task to trigger the "ledger_accept" method. + ledgerAcceptor = Executors.newScheduledThreadPool(1); + ledgerAcceptor.scheduleAtFixedRate(() -> LEDGER_ACCEPTOR.accept(this), + acceptIntervalMillis.toMillis(), + acceptIntervalMillis.toMillis(), + TimeUnit.MILLISECONDS + ); + + waitForLedgerTimeToSync(); + } + + /** + * Stops the automated Ledger Acceptor, for example to control an integration test more finely. + */ + @SuppressWarnings({"all"}) + public void stopLedgerAcceptor() { + try { + ledgerAcceptor.shutdown(); + ledgerAcceptor.awaitTermination(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException("Unable to stop ledger acceptor", e); + } + } } diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/TestnetEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/TestnetEnvironment.java index 7899944b5..6c8a506d9 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/TestnetEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/TestnetEnvironment.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -29,7 +29,7 @@ /** * XRPL testnet environment. */ -public abstract class TestnetEnvironment implements XrplEnvironment { +public abstract class TestnetEnvironment extends AbstractXrplEnvironment implements XrplEnvironment { private final FaucetClient faucetClient = FaucetClient.construct(HttpUrl.parse("https://faucet.altnet.rippletest.net")); diff --git a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/XrplEnvironment.java b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/XrplEnvironment.java index 1c0521ad9..e65e74e3d 100644 --- a/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/XrplEnvironment.java +++ b/xrpl4j-integration-tests/src/test/java/org/xrpl/xrpl4j/tests/environment/XrplEnvironment.java @@ -9,9 +9,9 @@ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * + * * http://www.apache.org/licenses/LICENSE-2.0 - * + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -25,9 +25,11 @@ import org.xrpl.xrpl4j.client.XrplClient; import org.xrpl.xrpl4j.model.transactions.Address; +import java.time.Duration; + /** - * Abstraction for the XRP Ledger environment that the integration tests talk to. Provides access to resources - * need to interact to the with the ledger. + * Abstraction for the XRP Ledger environment that the integration tests talk to. Provides access to resources need to + * interact to the with the ledger. */ public interface XrplEnvironment { @@ -37,15 +39,13 @@ public interface XrplEnvironment { * Gets the XRPL environment to use. * *

- * Uses local rippled container by default. - * Set -DuseTestnet to run against reporting mode testnet node - * Set -DuseClioTestnet to run against Clio testnet node - * Set -DuseDevnet to run against devnet + * Uses local rippled container by default. Set -DuseTestnet to run against reporting mode testnet node Set + * -DuseClioTestnet to run against Clio testnet node Set -DuseDevnet to run against devnet *

* * @return XRPL environment of the correct type. */ - static XrplEnvironment getConfiguredEnvironment() { + static XrplEnvironment getNewConfiguredEnvironment() { // Use local rippled container by default. // Set -DuseTestnet to run against reporting mode testnet node // Set -DuseClioTestnet to run against Clio testnet node @@ -55,8 +55,7 @@ static XrplEnvironment getConfiguredEnvironment() { boolean isClioTestnetEnabled = System.getProperty("useClioTestnet") != null; if (isTestnetEnabled) { logger.info( - "System property 'useTestnet' detected; Using Reporting mode only Testnet node for integration testing." - ); + "System property 'useTestnet' detected; Using Reporting mode only Testnet node for integration testing."); return new ReportingTestnetEnvironment(); } else if (isClioTestnetEnabled) { logger.info("System property 'useClioTestnet' detected; Using Clio Testnet node for integration testing."); @@ -85,14 +84,12 @@ static MainnetEnvironment getConfiguredMainnetEnvironment() { if (isClioEnabled) { logger.info( "System property 'useClioMainnet' detected; Using Clio mainnet node for integration tests that are run " + - "against mainnet." - ); + "against mainnet."); return new ClioMainnetEnvironment(); } else { logger.info( "System property 'useClioMainnet' was not detected; Using Reporting Mode mainnet node for integration tests " + - "that are run against mainnet." - ); + "that are run against mainnet."); return new ReportingMainnetEnvironment(); } } @@ -101,4 +98,24 @@ static MainnetEnvironment getConfiguredMainnetEnvironment() { void fundAccount(Address classicAddress); + /** + * Accept the next ledger on an ad-hoc basis. + */ + void acceptLedger(); + + /** + * Helper method to start the Ledger Acceptor on a regular interval. + * + * @param acceptIntervalMillis The interval, in milliseconds, between regular calls to the `ledger_accept` method. + * This method is responsible for accepting new transactions into the ledger. + * + * @see "https://xrpl.org/docs/references/http-websocket-apis/admin-api-methods/server-control-methods/ledger_accept" + */ + void startLedgerAcceptor(final Duration acceptIntervalMillis); + + /** + * Stops the automated Ledger Acceptor, for example to control an integration test more finely. + */ + void stopLedgerAcceptor(); + }