Skip to content

Commit

Permalink
Handle OracleSet integ transactions distinctly: Fill last_update_time…
Browse files Browse the repository at this point in the history
… from last_validated_ledger_close_time value to maintain clock synchronization

use fresh wallets for OracleSet integ tests

Use fresh wallets for OracleDelete integration tests
  • Loading branch information
ckeshava committed Feb 13, 2025
1 parent 44fb7e7 commit d8c22b6
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 12 deletions.
54 changes: 52 additions & 2 deletions tests/integration/it_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@
from xrpl.models.amounts.issued_currency_amount import IssuedCurrencyAmount
from xrpl.models.currencies.issued_currency import IssuedCurrency
from xrpl.models.currencies.xrp import XRP
from xrpl.models.requests import Ledger
from xrpl.models.transactions.account_set import AccountSet, AccountSetAsfFlag
from xrpl.models.transactions.amm_create import AMMCreate
from xrpl.models.transactions.oracle_set import OracleSet
from xrpl.models.transactions.trust_set import TrustSet, TrustSetFlag
from xrpl.transaction import sign_and_submit # noqa: F401 - needed for sync tests
from xrpl.transaction import ( # noqa: F401 - needed for sync tests
submit as submit_transaction_alias,
)
from xrpl.utils import ripple_time_to_posix
from xrpl.wallet import Wallet

JSON_RPC_URL = "http://127.0.0.1:5005"
Expand Down Expand Up @@ -151,7 +154,32 @@ def sign_and_reliable_submission(
client: SyncClient = JSON_RPC_CLIENT,
check_fee: bool = True,
) -> Response:
response = submit_transaction(transaction, wallet, client, check_fee=check_fee)
modified_transaction = transaction

# OracleSet transaction needs to set a field within a lower-bound
# of the last ledger close time. This is a workaround to ensure that such
# transactions do not fail with tecINVALID_UPDATE_TIME error
if isinstance(transaction, OracleSet):
transaction_json = transaction.to_dict()

# Fetch the last ledger close time
last_validated_ledger = client.request(Ledger(ledger_index="validated"))

# Use the last validated ledger close time as the last_update_time
# The local system clock is not synchronized with standalone rippled node.
# Empirical observations display a difference of ~1200 seconds between the two.
# Note: The cause of this discrepancy is the LEDGER_ACCEPT_TIME parameter set
# to 0.1 seconds. Further investigation is required toward future updates to
# this parameter.
transaction_json["last_update_time"] = ripple_time_to_posix(
last_validated_ledger.result["ledger"]["close_time"]
)

modified_transaction = Transaction.from_dict(transaction_json)

response = submit_transaction(
modified_transaction, wallet, client, check_fee=check_fee
)
client.request(LEDGER_ACCEPT_REQUEST)
return response

Expand All @@ -163,8 +191,30 @@ async def sign_and_reliable_submission_async(
client: AsyncClient = ASYNC_JSON_RPC_CLIENT,
check_fee: bool = True,
) -> Response:
modified_transaction = transaction

# OracleSet transaction needs to set a field within a lower-bound
# of the last ledger close time. This is a workaround to ensure that such
# transactions do not fail with tecINVALID_UPDATE_TIME error
if isinstance(transaction, OracleSet):
transaction_json = transaction.to_dict()

# Fetch the last ledger close time
last_validated_ledger = await client.request(Ledger(ledger_index="validated"))

# Use the last validated ledger close time as the last_update_time
# The local system clock is not synchronized with standalone rippled node.
# Empirical observations display a difference of ~1200 seconds between the two.
# Note: The cause of this discrepancy is the LEDGER_ACCEPT_TIME parameter set
# to 0.1 seconds. Further investigation is required toward future updates to
# this parameter.
transaction_json["last_update_time"] = ripple_time_to_posix(
last_validated_ledger.result["ledger"]["close_time"]
)
modified_transaction = Transaction.from_dict(transaction_json)

response = await submit_transaction_async(
transaction, wallet, client, check_fee=check_fee
modified_transaction, wallet, client, check_fee=check_fee
)
await client.request(LEDGER_ACCEPT_REQUEST)
return response
Expand Down
24 changes: 18 additions & 6 deletions tests/integration/transactions/test_delete_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from tests.integration.integration_test_case import IntegrationTestCase
from tests.integration.it_utils import (
fund_wallet_async,
sign_and_reliable_submission_async,
test_async_and_sync,
)
from tests.integration.reusable_values import WALLET
from xrpl.models import AccountObjects, AccountObjectType, OracleDelete, OracleSet
from xrpl.models.response import ResponseStatus
from xrpl.models.transactions.oracle_set import PriceData
from xrpl.utils import str_to_hex
from xrpl.wallet import Wallet

_PROVIDER = str_to_hex("chainlink")
_ASSET_CLASS = str_to_hex("currency")
Expand All @@ -18,11 +19,16 @@
class TestDeleteOracle(IntegrationTestCase):
@test_async_and_sync(globals())
async def test_basic(self, client):
# Use a fresh wallet for OracleSet transactions. Re-using the WALLET and
# DESTINATION variables could cause conflicts with other integration tests.
oracle_owner_wallet = Wallet.create()
await fund_wallet_async(oracle_owner_wallet)

oracle_id = self.value

# Create PriceOracle, to be deleted later
tx = OracleSet(
account=WALLET.address,
account=oracle_owner_wallet.address,
# unlike the integration tests for OracleSet transaction, we do not have to
# dynamically change the oracle_document_id for these integration tests.
# This is because the Oracle LedgerObject is deleted by the end of the test.
Expand All @@ -39,21 +45,27 @@ async def test_basic(self, client):
),
],
)
response = await sign_and_reliable_submission_async(tx, WALLET, client)
response = await sign_and_reliable_submission_async(
tx, oracle_owner_wallet, client
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

# Create PriceOracle to delete
tx = OracleDelete(
account=WALLET.address,
account=oracle_owner_wallet.address,
oracle_document_id=oracle_id,
)
response = await sign_and_reliable_submission_async(tx, WALLET, client)
response = await sign_and_reliable_submission_async(
tx, oracle_owner_wallet, client
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

# confirm that the PriceOracle was actually deleted
account_objects_response = await client.request(
AccountObjects(account=WALLET.address, type=AccountObjectType.ORACLE)
AccountObjects(
account=oracle_owner_wallet.address, type=AccountObjectType.ORACLE
)
)
self.assertEqual(len(account_objects_response.result["account_objects"]), 0)
15 changes: 11 additions & 4 deletions tests/integration/transactions/test_set_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from tests.integration.integration_test_case import IntegrationTestCase
from tests.integration.it_utils import (
fund_wallet_async,
sign_and_reliable_submission_async,
test_async_and_sync,
)
from tests.integration.reusable_values import WALLET
from xrpl.models import AccountObjects, AccountObjectType, OracleSet
from xrpl.models.response import ResponseStatus
from xrpl.models.transactions.oracle_set import PriceData
from xrpl.utils import str_to_hex
from xrpl.wallet import Wallet

_PROVIDER = str_to_hex("provider")
_ASSET_CLASS = str_to_hex("currency")
Expand All @@ -20,8 +21,10 @@
class TestSetOracle(IntegrationTestCase):
@test_async_and_sync(globals())
async def test_all_fields(self, client):
oracle_owner_wallet = Wallet.create()
await fund_wallet_async(oracle_owner_wallet)
tx = OracleSet(
account=WALLET.address,
account=oracle_owner_wallet.address,
# if oracle_document_id is not modified, the (sync, async) +
# (json, websocket) combination of integration tests will update the same
# oracle object using identical "LastUpdateTime". Updates to an oracle must
Expand All @@ -48,13 +51,17 @@ async def test_all_fields(self, client):
),
],
)
response = await sign_and_reliable_submission_async(tx, WALLET, client)
response = await sign_and_reliable_submission_async(
tx, oracle_owner_wallet, client
)
self.assertEqual(response.status, ResponseStatus.SUCCESS)
self.assertEqual(response.result["engine_result"], "tesSUCCESS")

# confirm that the PriceOracle was actually created
account_objects_response = await client.request(
AccountObjects(account=WALLET.address, type=AccountObjectType.ORACLE)
AccountObjects(
account=oracle_owner_wallet.address, type=AccountObjectType.ORACLE
)
)

# subsequent integration tests (sync/async + json/websocket) add one
Expand Down

0 comments on commit d8c22b6

Please sign in to comment.