diff --git a/algosdk/atomic_transaction_composer.py b/algosdk/atomic_transaction_composer.py index e30b8a13..66291934 100644 --- a/algosdk/atomic_transaction_composer.py +++ b/algosdk/atomic_transaction_composer.py @@ -16,7 +16,8 @@ from algosdk import abi, error, transaction from algosdk.transaction import GenericSignedTransaction from algosdk.abi.address_type import AddressType -from algosdk.v2client import algod +from algosdk.v2client import algod, models + # The first four bytes of an ABI method call return must have this hash ABI_RETURN_HASH = b"\x15\x1f\x7c\x75" @@ -255,7 +256,6 @@ def __init__( decode_error: Optional[Exception], tx_info: dict, method: abi.Method, - missing_signature: bool, ) -> None: self.tx_id = tx_id self.raw_value = raw_value @@ -263,7 +263,40 @@ def __init__( self.decode_error = decode_error self.tx_info = tx_info self.method = method - self.missing_signature = missing_signature + + +class SimulateEvalOverrides: + def __init__( + self, + *, + max_log_calls: Optional[int] = None, + max_log_size: Optional[int] = None, + allow_empty_signatures: Optional[bool] = None, + ) -> None: + self.max_log_calls = max_log_calls + self.max_log_size = max_log_size + self.allow_empty_signatures = allow_empty_signatures + + @staticmethod + def from_simulation_result( + simulation_result: Dict[str, Any] + ) -> Optional["SimulateEvalOverrides"]: + if "eval-overrides" not in simulation_result: + return None + + eval_override_dict = simulation_result.get("eval-overrides", dict()) + eval_override = SimulateEvalOverrides() + + if "max-log-calls" in eval_override_dict: + eval_override.max_log_calls = eval_override_dict["max-log-calls"] + if "max-log-size" in eval_override_dict: + eval_override.max_log_size = eval_override_dict["max-log-size"] + if "allow-empty-signatures" in eval_override_dict: + eval_override.allow_empty_signatures = eval_override_dict[ + "allow-empty-signatures" + ] + + return eval_override class SimulateAtomicTransactionResponse: @@ -275,6 +308,7 @@ def __init__( simulate_response: Dict[str, Any], tx_ids: List[str], results: List[SimulateABIResult], + eval_overrides: Optional[SimulateEvalOverrides] = None, ) -> None: self.version = version self.failure_message = failure_message @@ -282,6 +316,7 @@ def __init__( self.simulate_response = simulate_response self.tx_ids = tx_ids self.abi_results = results + self.eval_overrides = eval_overrides class AtomicTransactionComposer: @@ -684,7 +719,9 @@ def submit(self, client: algod.AlgodClient) -> List[str]: return self.tx_ids def simulate( - self, client: algod.AlgodClient + self, + client: algod.AlgodClient, + request: Optional[models.SimulateRequest] = None, ) -> SimulateAtomicTransactionResponse: """ Send the transaction group to the `simulate` endpoint and wait for results. @@ -694,6 +731,8 @@ def simulate( Args: client (AlgodClient): Algod V2 client + request (models.SimulateRequest): SimulateRequest with options in simulation. + The request's transaction group will be overrwritten by the composer's group, only simulation related options will be used. Returns: SimulateAtomicTransactionResponse: Object with simulation results for this @@ -711,9 +750,18 @@ def simulate( "lower to simulate a group" ) + current_simulation_request = ( + request if request else models.SimulateRequest(txn_groups=list()) + ) + current_simulation_request.txn_groups = [ + models.SimulateRequestTransactionGroup(txns=self.signed_txns) + ] + simulation_result = cast( - Dict[str, Any], client.simulate_raw_transactions(self.signed_txns) + Dict[str, Any], + client.simulate_transactions(current_simulation_request), ) + # Only take the first group in the simulate response txn_group: Dict[str, Any] = simulation_result["txn-groups"][0] @@ -752,7 +800,6 @@ def simulate( decode_error=result.decode_error, tx_info=result.tx_info, method=result.method, - missing_signature=sim_txn.get("missing-signature", False), ) ) @@ -763,6 +810,9 @@ def simulate( simulate_response=simulation_result, tx_ids=self.tx_ids, results=sim_results, + eval_overrides=SimulateEvalOverrides.from_simulation_result( + simulation_result + ), ) def execute( diff --git a/algosdk/v2client/models/simulate_request.py b/algosdk/v2client/models/simulate_request.py index 51d7ed63..909eb223 100644 --- a/algosdk/v2client/models/simulate_request.py +++ b/algosdk/v2client/models/simulate_request.py @@ -18,15 +18,25 @@ def dictify(self) -> Dict[str, Any]: class SimulateRequest(object): txn_groups: List[SimulateRequestTransactionGroup] + allow_more_logs: bool + allow_empty_signatures: bool def __init__( - self, *, txn_groups: List[SimulateRequestTransactionGroup] + self, + *, + txn_groups: List[SimulateRequestTransactionGroup], + allow_more_logs: bool = False, + allow_empty_signatures: bool = False, ) -> None: self.txn_groups = txn_groups + self.allow_more_logs = allow_more_logs + self.allow_empty_signatures = allow_empty_signatures def dictify(self) -> Dict[str, Any]: return { "txn-groups": [ txn_group.dictify() for txn_group in self.txn_groups - ] + ], + "allow-more-logging": self.allow_more_logs, + "allow-empty-signatures": self.allow_empty_signatures, } diff --git a/tests/integration.tags b/tests/integration.tags index 2ead9e95..d2c62ff4 100644 --- a/tests/integration.tags +++ b/tests/integration.tags @@ -14,3 +14,5 @@ @rekey_v1 @send @send.keyregtxn +@simulate +@simulate.lift_log_limits diff --git a/tests/steps/other_v2_steps.py b/tests/steps/other_v2_steps.py index 27c9bbf4..0d213911 100644 --- a/tests/steps/other_v2_steps.py +++ b/tests/steps/other_v2_steps.py @@ -31,6 +31,7 @@ ApplicationLocalState, DryrunRequest, DryrunSource, + SimulateRequest, ) from tests.steps.steps import algod_port, indexer_port from tests.steps.steps import token as daemon_token @@ -1445,10 +1446,14 @@ def simulate_transaction(context): @then("the simulation should succeed without any failure message") def simulate_transaction_succeed(context): - if hasattr(context, "simulate_response"): - assert context.simulate_response["would-succeed"] is True - else: - assert context.atomic_transaction_composer_return.would_succeed is True + resp = ( + context.simulate_response + if hasattr(context, "simulate_response") + else context.atomic_transaction_composer_return.simulate_response + ) + + for group in resp["txn-groups"]: + assert "failure-message" not in group @then("I simulate the current transaction group with the composer") @@ -1462,47 +1467,51 @@ def simulate_atc(context): 'the simulation should report a failure at group "{group}", path "{path}" with message "{message}"' ) def simulate_atc_failure(context, group, path, message): - resp: SimulateAtomicTransactionResponse = ( - context.atomic_transaction_composer_return - ) + if hasattr(context, "simulate_response"): + resp = context.simulate_response + else: + resp = context.atomic_transaction_composer_return.simulate_response group_idx: int = int(group) fail_path = ",".join( - [ - str(pe) - for pe in resp.simulate_response["txn-groups"][group_idx][ - "failed-at" - ] - ] + [str(pe) for pe in resp["txn-groups"][group_idx]["failed-at"]] ) - assert resp.would_succeed is False assert fail_path == path - assert message in resp.failure_message + assert message in resp["txn-groups"][group_idx]["failure-message"] -@when("I prepare the transaction without signatures for simulation") -def step_impl(context): - context.stx = transaction.SignedTransaction(context.txn, None) +@when("I make a new simulate request.") +def make_simulate_request(context): + context.simulate_request = SimulateRequest(txn_groups=[]) -@then( - 'the simulation should report missing signatures at group "{group}", transactions "{path}"' -) -def check_missing_signatures(context, group, path): - if hasattr(context, "simulate_response"): - resp = context.simulate_response - else: - resp = context.atomic_transaction_composer_return.simulate_response +@then("I allow more logs on that simulate request.") +def allow_more_logs_in_request(context): + context.simulate_request.allow_more_logs = True + + +@then("I simulate the transaction group with the simulate request.") +def simulate_group_with_request(context): + context.atomic_transaction_composer_return = ( + context.atomic_transaction_composer.simulate( + context.app_acl, context.simulate_request + ) + ) - group_idx: int = int(group) - tx_idxs: list[int] = [int(pe) for pe in path.split(",")] - assert resp["would-succeed"] is False +@then("I check the simulation result has power packs allow-more-logging.") +def power_pack_simulation_should_pass(context): + assert context.atomic_transaction_composer_return.eval_overrides + assert ( + context.atomic_transaction_composer_return.eval_overrides.max_log_calls + ) + assert ( + context.atomic_transaction_composer_return.eval_overrides.max_log_size + ) - for tx_idx in tx_idxs: - missing_sig = resp["txn-groups"][group_idx]["txn-results"][tx_idx][ - "missing-signature" - ] - assert missing_sig is True + +@when("I prepare the transaction without signatures for simulation") +def step_impl(context): + context.stx = transaction.SignedTransaction(context.txn, None) @when("we make a GetLedgerStateDelta call against round {round}")