Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simulation: Lift log limits option in SimulateRequest #469

Merged
merged 23 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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 .test-env
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Configs for testing repo download:
SDK_TESTING_URL="https://github.com/algorand/algorand-sdk-testing"
SDK_TESTING_BRANCH="master"
SDK_TESTING_BRANCH="simulate-request-log-option"
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
SDK_TESTING_HARNESS="test-harness"

INSTALL_ONLY=0
Expand Down
79 changes: 73 additions & 6 deletions algosdk/atomic_transaction_composer.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from algosdk.transaction import GenericSignedTransaction
from algosdk.abi.address_type import AddressType
from algosdk.v2client import algod
from algosdk.v2client import models
ahangsu marked this conversation as resolved.
Show resolved Hide resolved

# The first four bytes of an ABI method call return must have this hash
ABI_RETURN_HASH = b"\x15\x1f\x7c\x75"
Expand Down Expand Up @@ -255,35 +256,61 @@ 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
self.return_value = return_value
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,
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
max_log_size: Optional[int] = None,
) -> None:
self.max_log_calls = max_log_calls
self.max_log_size = max_log_size

@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"]

return eval_override


class SimulateAtomicTransactionResponse:
def __init__(
self,
version: int,
would_succeed: bool,
failure_message: str,
failed_at: Optional[List[int]],
simulate_response: Dict[str, Any],
tx_ids: List[str],
results: List[SimulateABIResult],
eval_overrides: Optional[SimulateEvalOverrides] = None,
) -> None:
self.version = version
self.would_succeed = would_succeed
self.failure_message = failure_message
self.failed_at = failed_at
self.simulate_response = simulate_response
self.tx_ids = tx_ids
self.abi_results = results
self.eval_overrides = eval_overrides


class AtomicTransactionComposer:
Expand Down Expand Up @@ -716,6 +743,9 @@ def simulate(
simulation_result = cast(
Dict[str, Any], client.simulate_raw_transactions(self.signed_txns)
)
return self.__report_simulation_response(simulation_result)

def __report_simulation_response(self, simulation_result: Dict[str, Any]):
# Only take the first group in the simulate response
txn_group: Dict[str, Any] = simulation_result["txn-groups"][0]

Expand Down Expand Up @@ -754,19 +784,56 @@ def simulate(
decode_error=result.decode_error,
tx_info=result.tx_info,
method=result.method,
missing_signature=sim_txn.get("missing-signature", False),
)
)

return SimulateAtomicTransactionResponse(
version=simulation_result.get("version", 0),
would_succeed=simulation_result.get("would-succeed", False),
failure_message=txn_group.get("failure-message", ""),
failed_at=txn_group.get("failed-at"),
simulate_response=simulation_result,
tx_ids=self.tx_ids,
results=sim_results,
eval_overrides=SimulateEvalOverrides.from_simulation_result(
simulation_result
),
)

def simulate_with_request(
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
self,
client: algod.AlgodClient,
request: models.SimulateRequest = models.SimulateRequest(
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
txn_groups=list()
),
):
current_simulation_request = request

if (
self.status < AtomicTransactionComposerStatus.BUILT
or self.status > AtomicTransactionComposerStatus.SUBMITTED
):
raise error.AtomicTransactionComposerError(
"AtomicTransactionComposerStatus must be in range [BUILT, SUBMITTED] "
"to simulate a group"
)
elif self.status == AtomicTransactionComposerStatus.BUILT:
unsigned_txn: List[transaction.GenericSignedTransaction] = [
transaction.SignedTransaction(txn_with_signer.txn, "")
for txn_with_signer in self.txn_list
]
current_simulation_request.txn_groups = [
models.SimulateRequestTransactionGroup(txns=unsigned_txn)
]
else:
current_simulation_request.txn_groups = [
models.SimulateRequestTransactionGroup(txns=self.signed_txns)
]

simulation_result = cast(
Dict[str, Any],
client.simulate_transactions(current_simulation_request),
)
return self.__report_simulation_response(simulation_result)

def execute(
self, client: algod.AlgodClient, wait_rounds: int
Expand Down
10 changes: 8 additions & 2 deletions algosdk/v2client/models/simulate_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ def dictify(self) -> Dict[str, Any]:

class SimulateRequest(object):
txn_groups: List[SimulateRequestTransactionGroup]
lift_log_limits: bool

def __init__(
self, *, txn_groups: List[SimulateRequestTransactionGroup]
self,
*,
txn_groups: List[SimulateRequestTransactionGroup],
lift_log_limits: bool = False,
) -> None:
self.txn_groups = txn_groups
self.lift_log_limits = lift_log_limits

def dictify(self) -> Dict[str, Any]:
return {
"txn-groups": [
txn_group.dictify() for txn_group in self.txn_groups
]
],
"allow-more-logging": self.lift_log_limits,
}
1 change: 1 addition & 0 deletions tests/integration.tags
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@
@send
@send.keyregtxn
@simulate
@simulate.lift_log_limits
45 changes: 39 additions & 6 deletions tests/steps/other_v2_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1441,9 +1442,12 @@ 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
assert len(context.simulate_response["failure-message"]) == 0
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
else:
assert context.atomic_transaction_composer_return.would_succeed is True
assert (
len(context.atomic_transaction_composer_return.failure_message)
== 0
)


@then("I simulate the current transaction group with the composer")
Expand All @@ -1469,11 +1473,42 @@ def simulate_atc_failure(context, group, path, message):
]
]
)
assert resp.would_succeed is False
assert fail_path == path
assert message in resp.failure_message


@when("I make a new simulate request.")
def make_simulate_request(context):
context.simulate_request = SimulateRequest(txn_groups=[])


@then("I lift log limits on that simulate request.")
def lift_log_limits_in_request(context):
context.simulate_request.lift_log_limits = True
ahangsu marked this conversation as resolved.
Show resolved Hide resolved


@then("I attach the simulate request to simulate the transaction group.")
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
def attach_sim_request_to_txn_group_simulation(context):
context.simulate_response = (
context.atomic_transaction_composer.simulate_with_request(
context.app_acl, context.simulate_request
)
)


@then('the simulation with "{power}" should not have failure message.')
def power_pack_simulation_should_pass(context, power: str):
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
packs = power.split(",")
if len(packs) > 0:
assert context.simulate_response.eval_overrides

if "allow-more-logging" in packs:
assert context.simulate_response.eval_overrides.max_log_calls
assert context.simulate_response.eval_overrides.max_log_size

assert len(context.simulate_response.failure_message) == 0


@when("I prepare the transaction without signatures for simulation")
def step_impl(context):
context.stx = transaction.SignedTransaction(context.txn, None)
Expand All @@ -1491,10 +1526,8 @@ def check_missing_signatures(context, group, path):
group_idx: int = int(group)
tx_idxs: list[int] = [int(pe) for pe in path.split(",")]

assert resp["would-succeed"] is False

for tx_idx in tx_idxs:
missing_sig = resp["txn-groups"][group_idx]["txn-results"][tx_idx][
"missing-signature"
"failure-message"
]
assert missing_sig is True