Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion multiversx_sdk/entrypoints/entrypoints_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def test_contract_flow(self):

tx_hash = self.entrypoint.send_transaction(transaction)
outcome = controller.await_completed_deploy(
tx_hash, AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=60000)
tx_hash, AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=30000)
)

assert len(outcome.contracts) == 1
Expand Down
14 changes: 7 additions & 7 deletions multiversx_sdk/network_providers/account_awaiter_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,29 +82,29 @@ def condition(account: AccountOnNetwork):

@pytest.mark.networkInteraction
def test_ensure_error_if_timeout(self):
alice = load_wallets()["alice"]
alice_address = Address.new_from_bech32(alice.label)
grace = load_wallets()["grace"]
grace_address = Address.new_from_bech32(grace.label)
bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")

Comment on lines +85 to 88
api = ApiNetworkProvider("https://devnet-api.multiversx.com")
watcher = AccountAwaiter(
fetcher=api,
polling_interval_in_milliseconds=1000,
timeout_interval_in_milliseconds=10000,
polling_interval_in_milliseconds=600,
timeout_interval_in_milliseconds=1000,
)

value = 100_000
transaction = Transaction(
sender=alice_address,
sender=grace_address,
receiver=bob,
gas_limit=50000,
chain_id="D",
value=value,
)
transaction.nonce = api.get_account(alice_address).nonce
transaction.nonce = api.get_account(grace_address).nonce

tx_computer = TransactionComputer()
transaction.signature = alice.secret_key.sign(tx_computer.compute_bytes_for_signing(transaction))
transaction.signature = grace.secret_key.sign(tx_computer.compute_bytes_for_signing(transaction))

initial_balance = api.get_account(bob).balance

Expand Down
13 changes: 6 additions & 7 deletions multiversx_sdk/network_providers/api_network_provider_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_get_network_config(self):

assert result.chain_id == "D"
assert result.gas_per_data_byte == 1500
assert result.round_duration == 6000
assert result.round_duration == 600
assert result.min_gas_limit == 50000
assert result.min_gas_price == 1_000_000_000
assert result.raw
Expand Down Expand Up @@ -175,7 +175,6 @@ def test_send_transactions(self):
assert num_txs == 2
assert hashes == expected_hashes

@pytest.mark.skip
def test_simulate_transaction(self):
bob = load_wallets()["bob"]
tx_computer = TransactionComputer()
Expand All @@ -201,7 +200,7 @@ def test_simulate_transaction(self):
transaction = Transaction(
sender=Address.new_from_bech32(bob.label),
receiver=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq076flgeualrdu5jyyj60snvrh7zu4qrg05vqez5jen"),
gas_limit=10000000,
gas_limit=1_500_000,
chain_id="D",
data=b"add@07",
nonce=nonce,
Expand Down Expand Up @@ -309,7 +308,7 @@ def test_send_and_await_for_completed_transaction(self):

tx_on_network = self.api.await_transaction_completed(
transaction_hash=hash,
options=AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=30000),
options=AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=20000),
)
assert tx_on_network.status.is_completed

Expand All @@ -327,7 +326,7 @@ def test_send_and_await_for_completed_transaction(self):

tx_on_network = self.api.await_transaction_completed(
transaction_hash=hash,
options=AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=30000),
options=AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=20000),
)
assert not tx_on_network.status.is_successful

Expand All @@ -351,7 +350,7 @@ def test_send_and_await_transaction_on_condition(self):

tx_on_network = self.api.await_transaction_completed(
transaction_hash=hash,
options=AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=30000),
options=AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=20000),
)
assert tx_on_network.status.is_completed

Expand All @@ -373,7 +372,7 @@ def condition(tx: TransactionOnNetwork) -> bool:
tx_on_network = self.api.await_transaction_on_condition(
transaction_hash=hash,
condition=condition,
options=AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=30000),
options=AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=20000),
)
assert tx_on_network.status.status == "fail"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def test_get_network_config(self):

assert result.chain_id == "D"
assert result.gas_per_data_byte == 1500
assert result.round_duration == 6000
assert result.round_duration == 600
assert result.min_gas_limit == 50000
assert result.min_gas_price == 1_000_000_000
assert result.raw
Expand Down Expand Up @@ -281,7 +281,6 @@ def test_send_transactions(self):
assert num_txs == 2
assert hashes == expected_hashes

@pytest.mark.skip
def test_simulate_transaction(self):
bob = load_wallets()["bob"]
tx_computer = TransactionComputer()
Expand All @@ -307,7 +306,7 @@ def test_simulate_transaction(self):
transaction = Transaction(
sender=Address.new_from_bech32(bob.label),
receiver=Address.new_from_bech32("erd1qqqqqqqqqqqqqpgq076flgeualrdu5jyyj60snvrh7zu4qrg05vqez5jen"),
gas_limit=10000000,
gas_limit=1_500_000,
chain_id="D",
data=b"add@07",
nonce=nonce,
Expand Down Expand Up @@ -375,7 +374,7 @@ def test_send_and_await_for_completed_transaction(self):
tx_on_network = self.proxy.await_transaction_completed(
hash,
options=AwaitingOptions(
polling_interval_in_milliseconds=6000,
polling_interval_in_milliseconds=600,
timeout_in_milliseconds=30000,
),
)
Expand All @@ -399,7 +398,7 @@ def condition(tx: TransactionOnNetwork) -> bool:
tx_on_network = self.proxy.await_transaction_on_condition(
transaction_hash=hash,
condition=condition,
options=AwaitingOptions(polling_interval_in_milliseconds=6000, timeout_in_milliseconds=30000),
options=AwaitingOptions(polling_interval_in_milliseconds=600, timeout_in_milliseconds=30000),
)
assert not tx_on_network.status.is_successful

Expand Down
66 changes: 46 additions & 20 deletions multiversx_sdk/network_providers/transaction_awaiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,57 @@ def _await_conditionally(
do_fetch: Callable[[], TransactionOnNetwork],
error: Exception,
) -> TransactionOnNetwork:
is_condition_satisfied = False
fetched_data: Union[TransactionOnNetwork, None] = None
max_number_of_retries = self.timeout_interval_in_milliseconds // self.polling_interval_in_milliseconds
max_number_of_attempts = self.timeout_interval_in_milliseconds // self.polling_interval_in_milliseconds
fetched_data, attempts_made = self._fetch_until_condition(
is_satisfied=is_satisfied,
do_fetch=do_fetch,
number_of_attempts=max_number_of_attempts,
)

if fetched_data is None:
raise error

if self.patience_time_in_milliseconds:
time.sleep(self.patience_time_in_milliseconds / ONE_SECOND_IN_MILLISECONDS)
remaining_attempts = max_number_of_attempts - attempts_made

# Keep the existing post-patience refresh in addition to the unused attempts.
refreshed_data, _ = self._fetch_until_condition(
is_satisfied=is_satisfied,
do_fetch=do_fetch,
number_of_attempts=remaining_attempts + 1,
)

if refreshed_data is not None:
return refreshed_data

logger.warning(
"Transaction status regressed after reaching the expected condition. "
"Returning the last satisfying response."
)

return fetched_data

def _fetch_until_condition(
self,
is_satisfied: Callable[[TransactionOnNetwork], bool],
do_fetch: Callable[[], TransactionOnNetwork],
number_of_attempts: int,
) -> tuple[Optional[TransactionOnNetwork], int]:
attempts_made = 0

while attempts_made < number_of_attempts:
attempts_made += 1

number_of_retries = 0
while number_of_retries < max_number_of_retries:
try:
fetched_data = do_fetch()
is_condition_satisfied = is_satisfied(fetched_data)

if is_condition_satisfied:
break
if is_satisfied(fetched_data):
return fetched_data, attempts_made
except TransactionFetchingError:
logger.warning("Couldn't fetch transaction. Retrying...")
except Exception as ex:
raise ex

number_of_retries += 1
time.sleep(self.polling_interval_in_milliseconds / ONE_SECOND_IN_MILLISECONDS)
if attempts_made < number_of_attempts:
time.sleep(self.polling_interval_in_milliseconds / ONE_SECOND_IN_MILLISECONDS)

if fetched_data is None or not is_condition_satisfied:
raise error

if self.patience_time_in_milliseconds:
time.sleep(self.patience_time_in_milliseconds / ONE_SECOND_IN_MILLISECONDS)
return do_fetch()

return fetched_data
return None, attempts_made
79 changes: 73 additions & 6 deletions multiversx_sdk/network_providers/transaction_awaiter_test.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from unittest.mock import patch

import pytest

from multiversx_sdk.core.address import Address
from multiversx_sdk.core.transaction import Transaction
from multiversx_sdk.core.transaction_computer import TransactionComputer
from multiversx_sdk.core.transaction_on_network import TransactionOnNetwork
from multiversx_sdk.core.transaction_status import TransactionStatus
from multiversx_sdk.network_providers.api_network_provider import ApiNetworkProvider
from multiversx_sdk.network_providers.errors import (
ExpectedTransactionStatusNotReachedError,
)
Expand All @@ -22,6 +23,17 @@
from multiversx_sdk.testutils.wallets import load_wallets


class TransactionSequenceFetcher:
def __init__(self, transactions: list[TransactionOnNetwork]) -> None:
self.transactions = transactions
self.number_of_fetches = 0

def get_transaction(self, transaction_hash: bytes | str) -> TransactionOnNetwork:
transaction = self.transactions[self.number_of_fetches]
self.number_of_fetches += 1
return transaction


class TestTransactionAwaiter:
provider = MockNetworkProvider()
watcher = TransactionAwaiter(
Expand Down Expand Up @@ -73,9 +85,9 @@ def test_await_status_not_executable(self):
@pytest.mark.networkInteraction
def test_on_network(self):
alice = load_wallets()["alice"]
proxy = ProxyNetworkProvider("https://devnet-api.multiversx.com")
proxy = ProxyNetworkProvider("https://devnet-gateway.multiversx.com")
watcher = TransactionAwaiter(
proxy, polling_interval_in_milliseconds=6000, timeout_interval_in_milliseconds=30000
proxy, polling_interval_in_milliseconds=1000, timeout_interval_in_milliseconds=20000
)
tx_computer = TransactionComputer()

Expand Down Expand Up @@ -116,17 +128,72 @@ def condition(tx: TransactionOnNetwork) -> bool:
tx_from_network = self.watcher.await_on_condition(tx_hash, condition)
assert tx_from_network.status.status == "failed"

def test_retries_after_status_regresses_during_patience(self):
pending = get_empty_transaction_on_network()
pending.status = TransactionStatus("pending")

first_completed = get_empty_transaction_on_network()
first_completed.status = TransactionStatus("success")

regressed = get_empty_transaction_on_network()
regressed.status = TransactionStatus("pending")

refreshed_completed = get_empty_transaction_on_network()
refreshed_completed.status = TransactionStatus("success")
refreshed_completed.raw["refreshed"] = True

fetcher = TransactionSequenceFetcher([pending, first_completed, regressed, refreshed_completed])
watcher = TransactionAwaiter(
fetcher=fetcher,
polling_interval_in_milliseconds=10,
timeout_interval_in_milliseconds=40,
patience_time_in_milliseconds=10,
)

with patch("multiversx_sdk.network_providers.transaction_awaiter.time.sleep"):
tx_from_network = watcher.await_completed("transaction-hash")

assert tx_from_network is refreshed_completed
assert tx_from_network.raw["refreshed"]
assert fetcher.number_of_fetches == 4

def test_returns_last_satisfying_response_after_remaining_attempts_are_exhausted(self):
pending = get_empty_transaction_on_network()
pending.status = TransactionStatus("pending")

completed = get_empty_transaction_on_network()
completed.status = TransactionStatus("success")

regressed_responses = [get_empty_transaction_on_network() for _ in range(3)]
for response in regressed_responses:
response.status = TransactionStatus("pending")

fetcher = TransactionSequenceFetcher([pending, completed, *regressed_responses])
watcher = TransactionAwaiter(
fetcher=fetcher,
polling_interval_in_milliseconds=10,
timeout_interval_in_milliseconds=40,
patience_time_in_milliseconds=10,
)

with patch("multiversx_sdk.network_providers.transaction_awaiter.time.sleep"):
tx_from_network = watcher.await_completed("transaction-hash")

assert tx_from_network is completed
assert tx_from_network.status.is_completed
assert fetcher.number_of_fetches == 5

@pytest.mark.networkInteraction
def test_ensure_error_if_timeout(self):
alice = load_wallets()["alice"]
alice_address = Address.new_from_bech32(alice.label)
bob = Address.new_from_bech32("erd1spyavw0956vq68xj8y4tenjpq2wd5a9p2c6j8gsz7ztyrnpxrruqzu66jx")

api = ApiNetworkProvider("https://devnet-api.multiversx.com")
api = ProxyNetworkProvider("https://devnet-gateway.multiversx.com")
watcher = TransactionAwaiter(
fetcher=api,
polling_interval_in_milliseconds=1000,
timeout_interval_in_milliseconds=10000,
polling_interval_in_milliseconds=600,
timeout_interval_in_milliseconds=1000,
)

transaction = Transaction(
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ allow-direct-references = true

[project]
name = "multiversx-sdk"
version = "3.0.0"
version = "3.0.1"
authors = [
{ name="MultiversX" },
]
Expand Down
Loading