From e1f9f0692c3d6e0c8e30d1db796cfa5da2ed77b7 Mon Sep 17 00:00:00 2001 From: Dafna Matsry Date: Wed, 29 May 2024 10:32:05 +0300 Subject: [PATCH] chore: sync repos --- Cargo.lock | 562 +++- Cargo.toml | 26 +- _blockifier_old | 1 - _mempool_old | 1 - _papyrus_old | 1 - crates/blockifier/Cargo.toml | 1 - crates/blockifier/bench/blockifier_bench.rs | 94 +- .../cairo1/account_faulty.cairo | 6 +- .../cairo1/account_with_long_validate.cairo | 2 +- .../cairo1/compiled/account_faulty.casm.json | 2911 +++++++++-------- .../account_with_dummy_validate.casm.json | 2 +- .../account_with_long_validate.casm.json | 2220 ++++++------- .../cairo1/compiled/empty_contract.casm.json | 3 +- .../compiled/legacy_test_contract.casm.json | 2 +- .../cairo1/compiled/test_contract.casm.json | 1696 ++++++---- crates/blockifier/src/blockifier.rs | 1 + crates/blockifier/src/blockifier/block.rs | 3 +- .../blockifier/src/blockifier/block_test.rs | 5 +- crates/blockifier/src/blockifier/config.rs | 11 + .../src/blockifier/stateful_validator.rs | 8 +- .../src/blockifier/transaction_executor.rs | 66 +- .../blockifier/transaction_executor_test.rs | 26 +- crates/blockifier/src/bouncer.rs | 42 +- crates/blockifier/src/bouncer_test.rs | 12 +- crates/blockifier/src/concurrency.rs | 5 +- .../blockifier/src/concurrency/fee_utils.rs | 30 + .../src/concurrency/fee_utils_test.rs | 48 + .../blockifier/src/concurrency/scheduler.rs | 225 +- .../src/concurrency/scheduler_test.rs | 322 +- .../blockifier/src/concurrency/test_utils.rs | 55 +- crates/blockifier/src/concurrency/utils.rs | 10 + ...oned_state_proxy.rs => versioned_state.rs} | 100 +- .../concurrency/versioned_state_proxy_test.rs | 244 -- .../src/concurrency/versioned_state_test.rs | 491 +++ .../src/concurrency/versioned_storage.rs | 26 +- .../src/concurrency/versioned_storage_test.rs | 33 + .../src/concurrency/worker_logic.rs | 262 ++ .../src/concurrency/worker_logic_test.rs | 302 ++ crates/blockifier/src/execution.rs | 1 + .../deprecated_syscalls_test.rs | 6 +- .../src/execution/deprecated_syscalls/mod.rs | 4 - .../src/execution/entry_point_test.rs | 574 +--- crates/blockifier/src/execution/errors.rs | 260 +- .../blockifier/src/execution/stack_trace.rs | 437 +++ .../src/execution/stack_trace_test.rs | 729 +++++ .../src/execution/syscalls/syscalls_test.rs | 6 +- crates/blockifier/src/fee/fee_checks.rs | 6 +- crates/blockifier/src/fee/fee_utils.rs | 11 + crates/blockifier/src/state/cached_state.rs | 245 +- .../blockifier/src/state/cached_state_test.rs | 34 +- crates/blockifier/src/state/state_api.rs | 13 +- crates/blockifier/src/test_utils.rs | 7 + crates/blockifier/src/test_utils/contracts.rs | 108 +- .../src/test_utils/dict_state_reader.rs | 2 +- .../src/transaction/account_transaction.rs | 47 +- .../transaction/account_transactions_test.rs | 168 +- .../blockifier/src/transaction/constants.rs | 1 + crates/blockifier/src/transaction/errors.rs | 40 +- crates/blockifier/src/transaction/objects.rs | 4 +- .../blockifier/src/transaction/test_utils.rs | 1 + .../src/transaction/transaction_execution.rs | 10 +- .../src/transaction/transactions.rs | 12 +- .../src/transaction/transactions_test.rs | 14 +- .../feature_contracts_compatibility_test.rs | 101 +- crates/gateway/Cargo.toml | 24 +- crates/gateway/src/config.rs | 219 +- crates/gateway/src/config_test.rs | 27 - crates/gateway/src/errors.rs | 14 +- crates/gateway/src/gateway.rs | 119 +- crates/gateway/src/gateway_test.rs | 83 +- .../gateway_config.json | 12 - crates/gateway/src/lib.rs | 5 +- crates/gateway/src/rpc_objects.rs | 89 +- crates/gateway/src/rpc_state_reader.rs | 146 +- crates/gateway/src/starknet_api_test_utils.rs | 418 ++- crates/gateway/src/state_reader.rs | 52 + crates/gateway/src/state_reader_test_utils.rs | 87 + .../src/stateful_transaction_validator.rs | 42 +- .../stateful_transaction_validator_test.rs | 47 +- .../src/stateless_transaction_validator.rs | 34 +- .../stateless_transaction_validator_test.rs | 38 +- crates/gateway/src/utils.rs | 45 +- crates/gateway/tests/fixtures/declare_v3.json | 94 - .../tests/fixtures/deploy_account_v3.json | 37 - crates/gateway/tests/fixtures/invoke_v3.json | 37 - crates/gateway/tests/routing_test.rs | 49 - crates/mempool/Cargo.toml | 12 +- crates/mempool/src/errors.rs | 4 - crates/mempool/src/lib.rs | 4 +- crates/mempool/src/mempool.rs | 185 +- crates/mempool/src/mempool_test.rs | 160 + crates/mempool/src/priority_queue.rs | 74 +- crates/mempool/src/priority_queue_test.rs | 58 - crates/mempool_infra/Cargo.toml | 13 + crates/mempool_infra/build.rs | 18 + .../proto/component_a_service.proto | 14 + .../proto/component_b_service.proto | 14 + crates/mempool_infra/src/component_client.rs | 36 + .../mempool_infra/src/component_client_rpc.rs | 3 + crates/mempool_infra/src/component_runner.rs | 12 +- .../src/component_runner_test.rs | 129 + crates/mempool_infra/src/component_server.rs | 51 + .../src/component_server_client_rpc_test.rs | 7 + .../src/component_server_client_test.rs | 173 + crates/mempool_infra/src/lib.rs | 6 +- crates/mempool_infra/src/network_component.rs | 4 + crates/mempool_node/Cargo.toml | 11 +- crates/mempool_node/src/bin/dump_config.rs | 10 + crates/mempool_node/src/config/config_test.rs | 108 +- crates/mempool_node/src/config/mod.rs | 9 +- .../src/test_files/mempool_node_config.json | 23 - crates/mempool_test_utils/Cargo.toml | 12 + crates/mempool_test_utils/src/lib.rs | 7 + crates/mempool_types/Cargo.toml | 17 + crates/mempool_types/src/errors.rs | 8 + crates/mempool_types/src/lib.rs | 3 + crates/mempool_types/src/mempool_types.rs | 129 + crates/mempool_types/src/utils.rs | 12 + crates/native_blockifier/BUILD | 26 + crates/native_blockifier/Cargo.toml | 47 + crates/native_blockifier/MANIFEST.in | 3 + crates/native_blockifier/README.md | 5 + .../native_blockifier/__init__.py | 1 + crates/native_blockifier/setup.cfg | 6 + crates/native_blockifier/setup.py | 11 + crates/native_blockifier/src/errors.rs | 113 + crates/native_blockifier/src/lib.rs | 79 + .../src/py_block_executor.rs | 578 ++++ .../src/py_block_executor_test.rs | 80 + crates/native_blockifier/src/py_declare.rs | 143 + .../src/py_deploy_account.rs | 105 + .../src/py_invoke_function.rs | 133 + crates/native_blockifier/src/py_l1_handler.rs | 40 + crates/native_blockifier/src/py_objects.rs | 83 + crates/native_blockifier/src/py_state_diff.rs | 157 + crates/native_blockifier/src/py_test_utils.rs | 23 + .../src/py_testing_wrappers.rs | 37 + .../native_blockifier/src/py_transaction.rs | 183 ++ crates/native_blockifier/src/py_utils.rs | 100 + crates/native_blockifier/src/py_validator.rs | 74 + crates/native_blockifier/src/state_readers.rs | 2 + .../src/state_readers/papyrus_state.rs | 139 + .../src/state_readers/papyrus_state_test.rs | 71 + .../src/state_readers/py_state_reader.rs | 120 + crates/native_blockifier/src/storage.rs | 304 ++ crates/native_blockifier/src/test_utils.rs | 58 + crates/papyrus_base_layer/Cargo.toml | 4 +- crates/papyrus_common/Cargo.toml | 6 +- crates/papyrus_common/src/block_hash.rs | 15 +- crates/papyrus_common/src/block_hash_test.rs | 2 +- crates/papyrus_common/src/class_hash_test.rs | 2 +- crates/papyrus_common/src/state.rs | 34 +- .../src/transaction_hash_test.rs | 2 +- crates/papyrus_config/Cargo.toml | 2 +- crates/papyrus_config/src/config_test.rs | 2 +- crates/papyrus_execution/Cargo.toml | 10 +- crates/papyrus_execution/src/test_utils.rs | 2 +- .../src/testing_instances.rs | 2 +- crates/papyrus_network/Cargo.toml | 9 +- crates/papyrus_network/build.rs | 13 - .../src/bin/streamed_bytes_benchmark.rs | 12 +- crates/papyrus_network/src/bin_utils/mod.rs | 6 +- crates/papyrus_network/src/converters/mod.rs | 19 +- .../src/converters/protobuf_conversion/mod.rs | 19 - .../protobuf_conversion/state_diff.rs | 238 -- crates/papyrus_network/src/converters/test.rs | 26 - crates/papyrus_network/src/db_executor/mod.rs | 178 +- .../papyrus_network/src/db_executor/test.rs | 71 +- .../papyrus_network/src/db_executor/utils.rs | 8 +- .../src/discovery/discovery_test.rs | 66 +- .../src/discovery/flow_test.rs | 30 +- .../src/discovery/identify_impl.rs | 31 +- .../papyrus_network/src/discovery/kad_impl.rs | 41 +- crates/papyrus_network/src/discovery/mod.rs | 68 +- crates/papyrus_network/src/gossipsub_impl.rs | 53 + crates/papyrus_network/src/lib.rs | 91 +- crates/papyrus_network/src/mixed_behaviour.rs | 51 +- .../src/network_manager/mod.rs | 239 +- .../src/network_manager/swarm_trait.rs | 40 +- .../src/network_manager/test.rs | 214 +- .../src/peer_manager/behaviour_impl.rs | 69 +- .../papyrus_network/src/peer_manager/mod.rs | 53 +- .../papyrus_network/src/peer_manager/peer.rs | 23 +- .../papyrus_network/src/peer_manager/test.rs | 49 +- .../src/protobuf_messages/mod.rs | 3 - .../src/{streamed_bytes => sqmr}/behaviour.rs | 93 +- .../behaviour_test.rs | 0 .../src/{streamed_bytes => sqmr}/flow_test.rs | 3 +- .../src/{streamed_bytes => sqmr}/handler.rs | 3 - .../handler/inbound_session.rs | 0 .../{streamed_bytes => sqmr}/handler_test.rs | 0 .../src/{streamed_bytes => sqmr}/messages.rs | 0 .../{streamed_bytes => sqmr}/messages_test.rs | 0 .../src/{streamed_bytes => sqmr}/mod.rs | 2 +- .../src/{streamed_bytes => sqmr}/protocol.rs | 0 .../{streamed_bytes => sqmr}/protocol_test.rs | 0 crates/papyrus_network/src/test_utils/mod.rs | 74 +- crates/papyrus_network/src/utils.rs | 67 + crates/papyrus_node/Cargo.toml | 6 +- .../examples/get_transaction_hash.rs | 143 +- crates/papyrus_node/src/config/config_test.rs | 2 +- crates/papyrus_node/src/main.rs | 11 +- crates/papyrus_node/src/main_test.rs | 2 +- crates/papyrus_p2p_sync/Cargo.toml | 5 +- crates/papyrus_p2p_sync/src/header.rs | 3 +- crates/papyrus_p2p_sync/src/header_test.rs | 32 +- crates/papyrus_p2p_sync/src/lib.rs | 13 +- .../papyrus_p2p_sync/src/state_diff_test.rs | 62 +- crates/papyrus_p2p_sync/src/stream_factory.rs | 30 +- crates/papyrus_p2p_sync/src/test_utils.rs | 9 +- crates/papyrus_proc_macros/Cargo.toml | 2 +- .../tests/latency_histogram.rs | 2 +- crates/papyrus_protobuf/Cargo.toml | 18 + crates/papyrus_protobuf/build.rs | 39 + crates/papyrus_protobuf/src/consensus.rs | 10 + .../src/converters}/common.rs | 87 +- .../src/converters/consensus.rs | 34 + .../src/converters}/header.rs | 157 +- .../src/converters/header_test.rs | 40 + crates/papyrus_protobuf/src/converters/mod.rs | 49 + .../src/converters/receipt.rs | 479 +++ .../src/converters/state_diff.rs | 345 ++ .../src/converters/transaction.rs | 1220 +++++++ crates/papyrus_protobuf/src/lib.rs | 6 + .../src}/proto/p2p/proto/common.proto | 14 +- .../src/proto/p2p/proto/consensus.proto | 10 + .../src}/proto/p2p/proto/header.proto | 28 +- .../src/proto/p2p/proto/receipt.proto | 78 + .../src}/proto/p2p/proto/state.proto | 6 +- .../src/proto/p2p/proto/transaction.proto | 162 + crates/papyrus_protobuf/src/protobuf.rs | 1 + crates/papyrus_protobuf/src/sync.rs | 78 + crates/papyrus_rpc/Cargo.toml | 7 +- .../papyrus_rpc/src/compression_utils_test.rs | 2 +- .../src/rpc_metrics/rpc_metrics_test.rs | 2 +- crates/papyrus_rpc/src/rpc_test.rs | 2 +- crates/papyrus_rpc/src/v0_6/api/api_impl.rs | 18 +- crates/papyrus_rpc/src/v0_6/api/test.rs | 20 +- .../src/v0_6/broadcasted_transaction_test.rs | 7 +- crates/papyrus_rpc/src/v0_6/execution_test.rs | 36 +- crates/papyrus_rpc/src/v0_6/transaction.rs | 69 - .../papyrus_rpc/src/v0_6/transaction_test.rs | 49 +- .../src/v0_6/write_api_result_test.rs | 2 +- crates/papyrus_rpc/src/v0_7/api/api_impl.rs | 40 +- crates/papyrus_rpc/src/v0_7/api/mod.rs | 9 + crates/papyrus_rpc/src/v0_7/api/test.rs | 73 +- .../src/v0_7/broadcasted_transaction_test.rs | 7 +- crates/papyrus_rpc/src/v0_7/execution_test.rs | 36 +- crates/papyrus_rpc/src/v0_7/transaction.rs | 69 - .../papyrus_rpc/src/v0_7/transaction_test.rs | 49 +- .../src/v0_7/write_api_result_test.rs | 2 +- crates/papyrus_storage/Cargo.toml | 4 +- crates/papyrus_storage/src/body/body_test.rs | 20 +- crates/papyrus_storage/src/body/events.rs | 365 +-- .../papyrus_storage/src/body/events_test.rs | 41 +- crates/papyrus_storage/src/body/mod.rs | 287 +- crates/papyrus_storage/src/class_test.rs | 2 +- .../src/compiled_class_test.rs | 2 +- .../src/compression_utils_test.rs | 2 +- crates/papyrus_storage/src/db/mod.rs | 7 +- .../papyrus_storage/src/db/serialization.rs | 29 +- .../src/db/table_types/dup_sort_tables.rs | 96 + .../db/table_types/dup_sort_tables_test.rs | 62 +- .../papyrus_storage/src/db/table_types/mod.rs | 19 + .../src/db/table_types/simple_table.rs | 18 + .../src/db/table_types/test_utils.rs | 64 +- .../src/deprecated/migrations_test.rs | 2 +- .../src/deprecated/test_instances.rs | 2 +- crates/papyrus_storage/src/lib.rs | 83 +- .../src/mmap_file/mmap_file_test.rs | 30 + crates/papyrus_storage/src/mmap_file/mod.rs | 28 +- .../src/serialization/serializers.rs | 168 +- .../src/serialization/serializers_test.rs | 25 +- .../papyrus_storage/src/state/state_test.rs | 2 +- crates/papyrus_storage/src/test_instances.rs | 65 +- crates/papyrus_storage/src/utils_test.rs | 2 +- crates/papyrus_sync/Cargo.toml | 6 +- crates/papyrus_sync/src/sync_test.rs | 2 +- .../Cargo.toml | 4 +- .../src/lib.rs | 44 + .../src/precision_test.rs | 0 .../papyrus_block_builder/Cargo.toml | 17 + .../papyrus_block_builder/src/lib.rs | 67 + .../papyrus_block_builder/src/test.rs | 32 + .../sequencing/papyrus_consensus/Cargo.toml | 16 + .../sequencing/papyrus_consensus/src/lib.rs | 7 + .../src/single_height_consensus.rs | 88 + .../src/single_height_consensus_test.rs | 126 + .../papyrus_consensus/src/test_utils.rs | 49 + .../sequencing/papyrus_consensus/src/types.rs | 141 + .../papyrus_consensus/src/types_test.rs | 10 + crates/starknet-api/src/block_hash.rs | 1 - .../starknet-api/src/block_hash/event_hash.rs | 26 - .../src/block_hash/event_hash_test.rs | 24 - crates/starknet-api/src/crypto_test.rs | 25 - crates/starknet-api/src/hash.rs | 307 -- crates/starknet-api/src/hash_test.rs | 93 - .../{starknet-api => starknet_api}/Cargo.toml | 3 +- .../scripts/rust_fmt.sh | 0 .../src/block.rs | 25 +- crates/starknet_api/src/block_hash.rs | 8 + .../src/block_hash/block_hash_calculator.rs | 142 + .../block_hash/block_hash_calculator_test.rs | 61 + .../src/block_hash/event_commitment.rs | 42 + .../src/block_hash/event_commitment_test.rs | 45 + .../src/block_hash/receipt_commitment.rs | 99 + .../src/block_hash/receipt_commitment_test.rs | 55 + .../src/block_hash/state_diff_hash.rs | 123 + .../src/block_hash/state_diff_hash_test.rs | 140 + .../starknet_api/src/block_hash/test_utils.rs | 77 + .../src/block_hash/transaction_commitment.rs | 53 + .../block_hash/transaction_commitment_test.rs | 51 + .../src/block_test.rs | 19 +- .../src/core.rs | 126 +- .../src/core_test.rs | 41 +- crates/starknet_api/src/crypto.rs | 2 + crates/starknet_api/src/crypto/crypto_test.rs | 27 + .../starknet_api/src/crypto/patricia_hash.rs | 132 + .../src/crypto/patricia_hash_test.rs | 28 + .../src/crypto/utils.rs} | 77 +- .../src/data_availability.rs | 28 +- .../src/deprecated_contract_class.rs | 3 +- .../src/external_transaction.rs | 4 +- .../src/external_transaction_test.rs | 35 +- crates/starknet_api/src/hash.rs | 51 + .../src/internal_transaction.rs | 0 .../{starknet-api => starknet_api}/src/lib.rs | 0 .../src/serde_utils.rs | 0 .../src/serde_utils_test.rs | 2 +- .../src/state.rs | 13 +- .../src/state_test.rs | 0 .../src/transaction.rs | 101 +- .../src/transaction_hash.rs | 122 +- .../src/type_utils.rs | 0 crates/starknet_client/Cargo.toml | 10 +- .../src/reader/objects/test_utils.rs | 2 +- .../src/writer/objects/response_test.rs | 2 +- .../src/writer/objects/test_utils.rs | 2 +- .../src/writer/objects/transaction_test.rs | 2 +- .../writer/starknet_gateway_client_test.rs | 2 +- crates/starknet_sierra_compile/Cargo.toml | 20 + crates/starknet_sierra_compile/src/compile.rs | 43 + .../src/compile_test.rs | 38 + crates/starknet_sierra_compile/src/lib.rs | 6 + .../starknet_sierra_compile/src/test_utils.rs | 33 + .../tests/fixtures/account_faulty.sierra.json | 1224 +++++++ crates/task_executor/Cargo.toml | 17 + crates/task_executor/src/executor.rs | 24 + crates/task_executor/src/lib.rs | 5 + crates/task_executor/src/tokio_executor.rs | 131 + .../task_executor/src/tokio_executor_test.rs | 23 + crates/tests-integration/Cargo.toml | 25 + .../src/integration_test_utils.rs | 49 + crates/tests-integration/src/lib.rs | 1 + .../tests/end_to_end_test.rs | 139 + 355 files changed, 21512 insertions(+), 8719 deletions(-) delete mode 160000 _blockifier_old delete mode 160000 _mempool_old delete mode 160000 _papyrus_old create mode 100644 crates/blockifier/src/blockifier/config.rs create mode 100644 crates/blockifier/src/concurrency/fee_utils.rs create mode 100644 crates/blockifier/src/concurrency/fee_utils_test.rs create mode 100644 crates/blockifier/src/concurrency/utils.rs rename crates/blockifier/src/concurrency/{versioned_state_proxy.rs => versioned_state.rs} (69%) delete mode 100644 crates/blockifier/src/concurrency/versioned_state_proxy_test.rs create mode 100644 crates/blockifier/src/concurrency/versioned_state_test.rs create mode 100644 crates/blockifier/src/concurrency/worker_logic.rs create mode 100644 crates/blockifier/src/concurrency/worker_logic_test.rs create mode 100644 crates/blockifier/src/execution/stack_trace.rs create mode 100644 crates/blockifier/src/execution/stack_trace_test.rs delete mode 100644 crates/gateway/src/config_test.rs delete mode 100644 crates/gateway/src/json_files_for_testing/gateway_config.json create mode 100644 crates/gateway/src/state_reader.rs create mode 100644 crates/gateway/src/state_reader_test_utils.rs delete mode 100644 crates/gateway/tests/fixtures/declare_v3.json delete mode 100644 crates/gateway/tests/fixtures/deploy_account_v3.json delete mode 100644 crates/gateway/tests/fixtures/invoke_v3.json delete mode 100644 crates/gateway/tests/routing_test.rs delete mode 100644 crates/mempool/src/errors.rs create mode 100644 crates/mempool/src/mempool_test.rs delete mode 100644 crates/mempool/src/priority_queue_test.rs create mode 100644 crates/mempool_infra/build.rs create mode 100644 crates/mempool_infra/proto/component_a_service.proto create mode 100644 crates/mempool_infra/proto/component_b_service.proto create mode 100644 crates/mempool_infra/src/component_client.rs create mode 100644 crates/mempool_infra/src/component_client_rpc.rs create mode 100644 crates/mempool_infra/src/component_runner_test.rs create mode 100644 crates/mempool_infra/src/component_server.rs create mode 100644 crates/mempool_infra/src/component_server_client_rpc_test.rs create mode 100644 crates/mempool_infra/src/component_server_client_test.rs create mode 100644 crates/mempool_node/src/bin/dump_config.rs delete mode 100644 crates/mempool_node/src/test_files/mempool_node_config.json create mode 100644 crates/mempool_test_utils/Cargo.toml create mode 100644 crates/mempool_test_utils/src/lib.rs create mode 100644 crates/mempool_types/Cargo.toml create mode 100644 crates/mempool_types/src/errors.rs create mode 100644 crates/mempool_types/src/lib.rs create mode 100644 crates/mempool_types/src/mempool_types.rs create mode 100644 crates/mempool_types/src/utils.rs create mode 100644 crates/native_blockifier/BUILD create mode 100644 crates/native_blockifier/Cargo.toml create mode 100644 crates/native_blockifier/MANIFEST.in create mode 100644 crates/native_blockifier/README.md create mode 100644 crates/native_blockifier/native_blockifier/__init__.py create mode 100644 crates/native_blockifier/setup.cfg create mode 100644 crates/native_blockifier/setup.py create mode 100644 crates/native_blockifier/src/errors.rs create mode 100644 crates/native_blockifier/src/lib.rs create mode 100644 crates/native_blockifier/src/py_block_executor.rs create mode 100644 crates/native_blockifier/src/py_block_executor_test.rs create mode 100644 crates/native_blockifier/src/py_declare.rs create mode 100644 crates/native_blockifier/src/py_deploy_account.rs create mode 100644 crates/native_blockifier/src/py_invoke_function.rs create mode 100644 crates/native_blockifier/src/py_l1_handler.rs create mode 100644 crates/native_blockifier/src/py_objects.rs create mode 100644 crates/native_blockifier/src/py_state_diff.rs create mode 100644 crates/native_blockifier/src/py_test_utils.rs create mode 100644 crates/native_blockifier/src/py_testing_wrappers.rs create mode 100644 crates/native_blockifier/src/py_transaction.rs create mode 100644 crates/native_blockifier/src/py_utils.rs create mode 100644 crates/native_blockifier/src/py_validator.rs create mode 100644 crates/native_blockifier/src/state_readers.rs create mode 100644 crates/native_blockifier/src/state_readers/papyrus_state.rs create mode 100644 crates/native_blockifier/src/state_readers/papyrus_state_test.rs create mode 100644 crates/native_blockifier/src/state_readers/py_state_reader.rs create mode 100644 crates/native_blockifier/src/storage.rs create mode 100644 crates/native_blockifier/src/test_utils.rs delete mode 100644 crates/papyrus_network/build.rs delete mode 100644 crates/papyrus_network/src/converters/protobuf_conversion/mod.rs delete mode 100644 crates/papyrus_network/src/converters/protobuf_conversion/state_diff.rs delete mode 100644 crates/papyrus_network/src/converters/test.rs create mode 100644 crates/papyrus_network/src/gossipsub_impl.rs delete mode 100644 crates/papyrus_network/src/protobuf_messages/mod.rs rename crates/papyrus_network/src/{streamed_bytes => sqmr}/behaviour.rs (85%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/behaviour_test.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/flow_test.rs (98%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/handler.rs (99%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/handler/inbound_session.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/handler_test.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/messages.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/messages_test.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/mod.rs (97%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/protocol.rs (100%) rename crates/papyrus_network/src/{streamed_bytes => sqmr}/protocol_test.rs (100%) create mode 100644 crates/papyrus_network/src/utils.rs create mode 100644 crates/papyrus_protobuf/Cargo.toml create mode 100644 crates/papyrus_protobuf/build.rs create mode 100644 crates/papyrus_protobuf/src/consensus.rs rename crates/{papyrus_network/src/converters/protobuf_conversion => papyrus_protobuf/src/converters}/common.rs (73%) create mode 100644 crates/papyrus_protobuf/src/converters/consensus.rs rename crates/{papyrus_network/src/converters/protobuf_conversion => papyrus_protobuf/src/converters}/header.rs (74%) create mode 100644 crates/papyrus_protobuf/src/converters/header_test.rs create mode 100644 crates/papyrus_protobuf/src/converters/mod.rs create mode 100644 crates/papyrus_protobuf/src/converters/receipt.rs create mode 100644 crates/papyrus_protobuf/src/converters/state_diff.rs create mode 100644 crates/papyrus_protobuf/src/converters/transaction.rs create mode 100644 crates/papyrus_protobuf/src/lib.rs rename crates/{papyrus_network/src/protobuf_messages => papyrus_protobuf/src}/proto/p2p/proto/common.proto (89%) create mode 100644 crates/papyrus_protobuf/src/proto/p2p/proto/consensus.proto rename crates/{papyrus_network/src/protobuf_messages => papyrus_protobuf/src}/proto/p2p/proto/header.proto (57%) create mode 100644 crates/papyrus_protobuf/src/proto/p2p/proto/receipt.proto rename crates/{papyrus_network/src/protobuf_messages => papyrus_protobuf/src}/proto/p2p/proto/state.proto (90%) create mode 100644 crates/papyrus_protobuf/src/proto/p2p/proto/transaction.proto create mode 100644 crates/papyrus_protobuf/src/protobuf.rs create mode 100644 crates/papyrus_protobuf/src/sync.rs rename crates/{test_utils => papyrus_test_utils}/Cargo.toml (88%) rename crates/{test_utils => papyrus_test_utils}/src/lib.rs (95%) rename crates/{test_utils => papyrus_test_utils}/src/precision_test.rs (100%) create mode 100644 crates/sequencing/papyrus_block_builder/Cargo.toml create mode 100644 crates/sequencing/papyrus_block_builder/src/lib.rs create mode 100644 crates/sequencing/papyrus_block_builder/src/test.rs create mode 100644 crates/sequencing/papyrus_consensus/Cargo.toml create mode 100644 crates/sequencing/papyrus_consensus/src/lib.rs create mode 100644 crates/sequencing/papyrus_consensus/src/single_height_consensus.rs create mode 100644 crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs create mode 100644 crates/sequencing/papyrus_consensus/src/test_utils.rs create mode 100644 crates/sequencing/papyrus_consensus/src/types.rs create mode 100644 crates/sequencing/papyrus_consensus/src/types_test.rs delete mode 100644 crates/starknet-api/src/block_hash.rs delete mode 100644 crates/starknet-api/src/block_hash/event_hash.rs delete mode 100644 crates/starknet-api/src/block_hash/event_hash_test.rs delete mode 100644 crates/starknet-api/src/crypto_test.rs delete mode 100644 crates/starknet-api/src/hash.rs delete mode 100644 crates/starknet-api/src/hash_test.rs rename crates/{starknet-api => starknet_api}/Cargo.toml (90%) rename crates/{starknet-api => starknet_api}/scripts/rust_fmt.sh (100%) rename crates/{starknet-api => starknet_api}/src/block.rs (86%) create mode 100644 crates/starknet_api/src/block_hash.rs create mode 100644 crates/starknet_api/src/block_hash/block_hash_calculator.rs create mode 100644 crates/starknet_api/src/block_hash/block_hash_calculator_test.rs create mode 100644 crates/starknet_api/src/block_hash/event_commitment.rs create mode 100644 crates/starknet_api/src/block_hash/event_commitment_test.rs create mode 100644 crates/starknet_api/src/block_hash/receipt_commitment.rs create mode 100644 crates/starknet_api/src/block_hash/receipt_commitment_test.rs create mode 100644 crates/starknet_api/src/block_hash/state_diff_hash.rs create mode 100644 crates/starknet_api/src/block_hash/state_diff_hash_test.rs create mode 100644 crates/starknet_api/src/block_hash/test_utils.rs create mode 100644 crates/starknet_api/src/block_hash/transaction_commitment.rs create mode 100644 crates/starknet_api/src/block_hash/transaction_commitment_test.rs rename crates/{starknet-api => starknet_api}/src/block_test.rs (63%) rename crates/{starknet-api => starknet_api}/src/core.rs (70%) rename crates/{starknet-api => starknet_api}/src/core_test.rs (58%) create mode 100644 crates/starknet_api/src/crypto.rs create mode 100644 crates/starknet_api/src/crypto/crypto_test.rs create mode 100644 crates/starknet_api/src/crypto/patricia_hash.rs create mode 100644 crates/starknet_api/src/crypto/patricia_hash_test.rs rename crates/{starknet-api/src/crypto.rs => starknet_api/src/crypto/utils.rs} (60%) rename crates/{starknet-api => starknet_api}/src/data_availability.rs (71%) rename crates/{starknet-api => starknet_api}/src/deprecated_contract_class.rs (98%) rename crates/{starknet-api => starknet_api}/src/external_transaction.rs (98%) rename crates/{starknet-api => starknet_api}/src/external_transaction_test.rs (70%) create mode 100644 crates/starknet_api/src/hash.rs rename crates/{starknet-api => starknet_api}/src/internal_transaction.rs (100%) rename crates/{starknet-api => starknet_api}/src/lib.rs (100%) rename crates/{starknet-api => starknet_api}/src/serde_utils.rs (100%) rename crates/{starknet-api => starknet_api}/src/serde_utils_test.rs (99%) rename crates/{starknet-api => starknet_api}/src/state.rs (97%) rename crates/{starknet-api => starknet_api}/src/state_test.rs (100%) rename crates/{starknet-api => starknet_api}/src/transaction.rs (90%) rename crates/{starknet-api => starknet_api}/src/transaction_hash.rs (86%) rename crates/{starknet-api => starknet_api}/src/type_utils.rs (100%) create mode 100644 crates/starknet_sierra_compile/Cargo.toml create mode 100644 crates/starknet_sierra_compile/src/compile.rs create mode 100644 crates/starknet_sierra_compile/src/compile_test.rs create mode 100644 crates/starknet_sierra_compile/src/lib.rs create mode 100644 crates/starknet_sierra_compile/src/test_utils.rs create mode 100644 crates/starknet_sierra_compile/tests/fixtures/account_faulty.sierra.json create mode 100644 crates/task_executor/Cargo.toml create mode 100644 crates/task_executor/src/executor.rs create mode 100644 crates/task_executor/src/lib.rs create mode 100644 crates/task_executor/src/tokio_executor.rs create mode 100644 crates/task_executor/src/tokio_executor_test.rs create mode 100644 crates/tests-integration/Cargo.toml create mode 100644 crates/tests-integration/src/integration_test_utils.rs create mode 100644 crates/tests-integration/src/lib.rs create mode 100644 crates/tests-integration/tests/end_to_end_test.rs diff --git a/Cargo.lock b/Cargo.lock index 69424d08684..0ab3206d9fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -161,6 +161,12 @@ version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + [[package]] name = "ark-ec" version = "0.4.2" @@ -1032,7 +1038,7 @@ dependencies = [ [[package]] name = "blockifier" version = "0.7.0-dev.1" -source = "git+https://github.com/starkware-libs/blockifier.git?rev=ba72c863#ba72c8631da01711d785d0b69981301bbb6e6507" +source = "git+https://github.com/starkware-libs/blockifier.git?rev=6babc28a#6babc28a5714a5e8ba2d3d84895c41d4f612a40f" dependencies = [ "anyhow", "ark-ec", @@ -1062,7 +1068,7 @@ dependencies = [ "serde_json", "sha3", "starknet-crypto 0.5.2", - "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?rev=1b46b42)", + "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool)", "strum 0.24.1", "strum_macros 0.24.3", "thiserror", @@ -1202,7 +1208,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10d9c31baeb6b52586b5adc88f01e90f86389d63d94363c562de5c79352e545b" dependencies = [ "cairo-lang-utils", - "indoc", + "indoc 2.0.5", "num-bigint 0.4.5", "num-traits 0.2.19", "parity-scale-codec", @@ -1353,7 +1359,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-utils", "indent", - "indoc", + "indoc 2.0.5", "itertools 0.11.0", "salsa", "smol_str", @@ -1431,7 +1437,7 @@ dependencies = [ "cairo-lang-syntax", "cairo-lang-utils", "id-arena", - "indoc", + "indoc 2.0.5", "itertools 0.11.0", "num-bigint 0.4.5", "num-traits 0.2.19", @@ -1533,7 +1539,7 @@ dependencies = [ "cairo-lang-sierra-gas", "cairo-lang-sierra-type-size", "cairo-lang-utils", - "indoc", + "indoc 2.0.5", "itertools 0.11.0", "num-bigint 0.4.5", "num-traits 0.2.19", @@ -1572,7 +1578,7 @@ dependencies = [ "cairo-lang-utils", "const_format", "indent", - "indoc", + "indoc 2.0.5", "itertools 0.11.0", "once_cell", "serde", @@ -3401,7 +3407,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35bd3cf68c183738046838e300353e4716c674dc5e56890de4826801a6622a28" dependencies = [ "futures-io", - "rustls", + "rustls 0.21.12", ] [[package]] @@ -3927,12 +3933,24 @@ dependencies = [ "http", "hyper", "log", - "rustls", + "rustls 0.21.12", "rustls-native-certs", "tokio", "tokio-rustls", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -4149,6 +4167,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" + [[package]] name = "indoc" version = "2.0.5" @@ -4325,7 +4349,7 @@ dependencies = [ "tokio-util", "tracing", "url", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -4878,6 +4902,7 @@ dependencies = [ "futures", "instant", "libp2p-core", + "libp2p-gossipsub", "libp2p-identify", "libp2p-identity", "libp2p-kad", @@ -4945,7 +4970,7 @@ dependencies = [ "quinn", "rand 0.8.5", "ring 0.16.20", - "rustls", + "rustls 0.21.12", "socket2 0.5.7", "thiserror", "tokio", @@ -5038,8 +5063,8 @@ dependencies = [ "libp2p-identity", "rcgen", "ring 0.16.20", - "rustls", - "rustls-webpki", + "rustls 0.21.12", + "rustls-webpki 0.101.7", "thiserror", "x509-parser", "yasna", @@ -5242,34 +5267,55 @@ dependencies = [ "libc", ] +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg 1.3.0", +] + [[package]] name = "mempool_infra" version = "0.4.0-dev.2" dependencies = [ + "assert_matches", "async-trait", + "papyrus_config", + "pretty_assertions", + "prost", + "protoc-prebuilt", + "serde", "thiserror", "tokio", + "tonic", + "tonic-build", ] [[package]] name = "mempool_node" version = "0.4.0-dev.2" dependencies = [ + "assert-json-diff", "assert_matches", - "async-trait", "clap 4.5.4", + "colored", "const_format", - "papyrus_config 0.3.0", + "mempool_test_utils", + "papyrus_config", "pretty_assertions", "serde", "serde_json", - "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?rev=1b46b42)", "starknet_gateway", - "thiserror", "tokio", "validator", ] +[[package]] +name = "mempool_test_utils" +version = "0.4.0-dev.2" + [[package]] name = "metrics" version = "0.21.1" @@ -5534,6 +5580,29 @@ dependencies = [ "tempfile", ] +[[package]] +name = "native_blockifier" +version = "0.4.0-dev.2" +dependencies = [ + "blockifier 0.4.0-dev.2", + "cached", + "cairo-lang-casm", + "cairo-lang-starknet-classes", + "cairo-vm", + "indexmap 2.2.6", + "log", + "num-bigint 0.4.5", + "papyrus_storage", + "pretty_assertions", + "pyo3", + "pyo3-log", + "serde", + "serde_json", + "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile", + "thiserror", +] + [[package]] name = "ndarray" version = "0.13.1" @@ -6042,7 +6111,7 @@ dependencies = [ "async-trait", "ethers", "ethers-core", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "pretty_assertions", "rustc-hex", "serde", @@ -6056,6 +6125,18 @@ dependencies = [ "url", ] +[[package]] +name = "papyrus_block_builder" +version = "0.4.0-dev.2" +dependencies = [ + "papyrus_storage", + "papyrus_test_utils", + "pretty_assertions", + "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", + "tracing", +] + [[package]] name = "papyrus_common" version = "0.4.0-dev.2" @@ -6064,47 +6145,45 @@ dependencies = [ "bitvec", "cairo-lang-starknet-classes", "hex", + "indexmap 2.2.6", "lazy_static", + "papyrus_test_utils", "pretty_assertions", + "rand 0.8.5", "serde", "serde_json", "sha3", "starknet-crypto 0.5.2", "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", - "test_utils", "thiserror", ] [[package]] name = "papyrus_config" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f71a47a327032490c0da1066d984cc67833669cf9a80fabb012bb2d491a761" +version = "0.4.0-dev.2" dependencies = [ + "assert_matches", "clap 4.5.4", "itertools 0.10.5", + "lazy_static", + "papyrus_test_utils", "serde", "serde_json", "strum_macros 0.25.3", + "tempfile", "thiserror", "validator", ] [[package]] -name = "papyrus_config" +name = "papyrus_consensus" version = "0.4.0-dev.2" dependencies = [ - "assert_matches", - "clap 4.5.4", - "itertools 0.10.5", - "lazy_static", - "serde", - "serde_json", - "strum_macros 0.25.3", - "tempfile", - "test_utils", - "thiserror", - "validator", + "async-trait", + "futures", + "mockall", + "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio", ] [[package]] @@ -6123,15 +6202,15 @@ dependencies = [ "lazy_static", "once_cell", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_storage", + "papyrus_test_utils", "pretty_assertions", "rand 0.8.5", "rand_chacha 0.3.1", "serde", "serde_json", "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", - "test_utils", "thiserror", "tracing", ] @@ -6164,7 +6243,7 @@ dependencies = [ "metrics 0.21.1", "metrics-exporter-prometheus", "metrics-process", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_storage", "pretty_assertions", "rand 0.8.5", @@ -6199,12 +6278,11 @@ dependencies = [ "metrics 0.21.1", "mockall", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", + "papyrus_protobuf", "papyrus_storage", "pretty_assertions", "prost", - "prost-build", - "prost-types", "rand 0.8.5", "replace_with", "serde", @@ -6235,15 +6313,18 @@ dependencies = [ "lazy_static", "libmdbx", "metrics-exporter-prometheus", + "once_cell", "papyrus_base_layer", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_monitoring_gateway", "papyrus_network", "papyrus_p2p_sync", + "papyrus_protobuf", "papyrus_rpc", "papyrus_storage", "papyrus_sync", + "papyrus_test_utils", "pretty_assertions", "reqwest", "serde", @@ -6252,7 +6333,6 @@ dependencies = [ "starknet_client", "strum 0.25.0", "tempfile", - "test_utils", "thiserror", "tokio", "tokio-stream", @@ -6273,15 +6353,16 @@ dependencies = [ "lazy_static", "metrics 0.21.1", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_network", "papyrus_proc_macros", + "papyrus_protobuf", "papyrus_storage", + "papyrus_test_utils", "rand 0.8.5", "serde", "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions", - "test_utils", "thiserror", "tokio", "tokio-stream", @@ -6295,10 +6376,24 @@ dependencies = [ "metrics 0.21.1", "metrics-exporter-prometheus", "papyrus_common", + "papyrus_test_utils", "prometheus-parse", "quote", "syn 2.0.61", - "test_utils", +] + +[[package]] +name = "papyrus_protobuf" +version = "0.4.0-dev.2" +dependencies = [ + "bytes", + "indexmap 2.2.6", + "primitive-types", + "prost", + "prost-build", + "prost-types", + "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", + "thiserror", ] [[package]] @@ -6330,10 +6425,11 @@ dependencies = [ "metrics-exporter-prometheus", "mockall", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_execution", "papyrus_proc_macros", "papyrus_storage", + "papyrus_test_utils", "pretty_assertions", "prometheus-parse", "rand 0.8.5", @@ -6347,7 +6443,6 @@ dependencies = [ "starknet_client", "strum 0.25.0", "strum_macros 0.25.3", - "test_utils", "thiserror", "tokio", "tokio-stream", @@ -6382,8 +6477,9 @@ dependencies = [ "num-traits 0.2.19", "page_size", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_proc_macros", + "papyrus_test_utils", "parity-scale-codec", "paste", "pretty_assertions", @@ -6400,7 +6496,6 @@ dependencies = [ "tempfile", "test-case", "test-log", - "test_utils", "thiserror", "tokio", "tracing", @@ -6428,16 +6523,16 @@ dependencies = [ "mockall", "papyrus_base_layer", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", "papyrus_proc_macros", "papyrus_storage", + "papyrus_test_utils", "pretty_assertions", "reqwest", "serde", "simple_logger", "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", "starknet_client", - "test_utils", "thiserror", "tokio", "tokio-stream", @@ -6445,11 +6540,32 @@ dependencies = [ "url", ] +[[package]] +name = "papyrus_test_utils" +version = "0.4.0-dev.2" +dependencies = [ + "cairo-lang-casm", + "cairo-lang-starknet-classes", + "cairo-lang-utils", + "indexmap 2.2.6", + "num-bigint 0.4.5", + "pretty_assertions", + "primitive-types", + "prometheus-parse", + "rand 0.8.5", + "rand_chacha 0.3.1", + "reqwest", + "serde", + "serde_json", + "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tracing", +] + [[package]] name = "parity-scale-codec" -version = "3.6.5" +version = "3.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dec8a8073036902368c2cdc0387e85ff9a37054d7e7c98e592145e0c92cd4fb" +checksum = "881331e34fa842a2fb61cc2db9643a8fedc615e47cfcc52597d1af0db9a7e8fe" dependencies = [ "arrayvec", "bitvec", @@ -7047,9 +7163,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -7078,9 +7194,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.5" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9554e3ab233f0a932403704f1a1d08c30d5ccd931adfdfa1e8b5a19b52c1d55a" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", @@ -7098,6 +7214,16 @@ dependencies = [ "prost", ] +[[package]] +name = "protoc-prebuilt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d85d4641fe3b8c6e853dfd09fe35379bc6b6e66bd692ac29ed4f7087de69ed5" +dependencies = [ + "ureq", + "zip", +] + [[package]] name = "psl-types" version = "2.0.11" @@ -7114,6 +7240,79 @@ dependencies = [ "psl-types", ] +[[package]] +name = "pyo3" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e681a6cfdc4adcc93b4d3cf993749a4552018ee0a9b65fc0ccfad74352c72a38" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "indoc 1.0.9", + "libc", + "memoffset", + "num-bigint 0.4.5", + "parking_lot 0.12.2", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076c73d0bc438f7a4ef6fdd0c3bb4732149136abd952b110ac93e4edb13a6ba5" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e53cee42e77ebe256066ba8aa77eff722b3bb91f3419177cf4cd0f304d3284d9" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-log" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c09c2b349b6538d8a73d436ca606dab6ce0aaab4dad9e6b7bdd57a4f556c3bc3" +dependencies = [ + "arc-swap", + "log", + "pyo3", +] + +[[package]] +name = "pyo3-macros" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfeb4c99597e136528c6dd7d5e3de5434d1ceaf487436a3f03b2d56b6fc9efd1" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "947dc12175c254889edc0c02e399476c2f652b4b9ebd123aa655c224de259536" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quanta" version = "0.11.1" @@ -7183,7 +7382,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash", - "rustls", + "rustls 0.21.12", "thiserror", "tokio", "tracing", @@ -7199,7 +7398,7 @@ dependencies = [ "rand 0.8.5", "ring 0.16.20", "rustc-hash", - "rustls", + "rustls 0.21.12", "slab", "thiserror", "tinyvec", @@ -7557,7 +7756,7 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.12", "rustls-pemfile", "serde", "serde_json", @@ -7573,7 +7772,7 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg", ] @@ -7786,10 +7985,24 @@ checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", "ring 0.17.8", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +dependencies = [ + "log", + "ring 0.17.8", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -7811,6 +8024,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -7821,6 +8040,17 @@ dependencies = [ "untrusted 0.9.0", ] +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "rustversion" version = "1.0.16" @@ -8565,6 +8795,7 @@ name = "starknet_api" version = "0.4.0-dev.2" dependencies = [ "assert_matches", + "bitvec", "cairo-lang-starknet-classes", "derive_more", "hex", @@ -8575,6 +8806,7 @@ dependencies = [ "rstest", "serde", "serde_json", + "sha3", "starknet-crypto 0.5.2", "starknet-types-core", "strum 0.24.1", @@ -8607,7 +8839,7 @@ dependencies = [ [[package]] name = "starknet_api" version = "0.12.0-dev.1" -source = "git+https://github.com/starkware-libs/starknet-api.git?rev=1b46b42#1b46b42086f3161738a71f87efb49b62dde4b841" +source = "git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool#859f491205e293c71dd22ca13ae72d92a9aed51b" dependencies = [ "cairo-lang-starknet-classes", "derive_more", @@ -8639,7 +8871,8 @@ dependencies = [ "mockito", "os_info", "papyrus_common", - "papyrus_config 0.4.0-dev.2", + "papyrus_config", + "papyrus_test_utils", "pretty_assertions", "rand 0.8.5", "rand_chacha 0.3.1", @@ -8651,7 +8884,6 @@ dependencies = [ "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.25.0", "strum_macros 0.25.3", - "test_utils", "thiserror", "tokio", "tokio-retry", @@ -8665,19 +8897,20 @@ version = "0.4.0-dev.2" dependencies = [ "assert_matches", "axum", - "blockifier 0.7.0-dev.1 (git+https://github.com/starkware-libs/blockifier.git?rev=ba72c863)", - "clap 4.5.4", - "papyrus_config 0.3.0", + "blockifier 0.7.0-dev.1 (git+https://github.com/starkware-libs/blockifier.git?rev=6babc28a)", + "cairo-lang-starknet-classes", + "mempool_infra", + "papyrus_config", "pretty_assertions", "reqwest", "rstest", "serde", "serde_json", - "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?rev=1b46b42)", + "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool)", + "starknet_mempool", + "starknet_mempool_types", "thiserror", "tokio", - "tower", - "url", "validator", ] @@ -8685,13 +8918,69 @@ dependencies = [ name = "starknet_mempool" version = "0.4.0-dev.2" dependencies = [ + "anyhow", "assert_matches", + "async-trait", "derive_more", - "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?rev=1b46b42)", + "mempool_infra", + "pretty_assertions", + "rstest", + "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool)", + "starknet_mempool_types", + "tokio", +] + +[[package]] +name = "starknet_mempool_integration_tests" +version = "0.4.0-dev.2" +dependencies = [ + "axum", + "hyper", + "mempool_infra", + "pretty_assertions", + "reqwest", + "rstest", + "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool)", + "starknet_gateway", + "starknet_mempool", + "starknet_mempool_types", + "tokio", +] + +[[package]] +name = "starknet_mempool_types" +version = "0.4.0-dev.2" +dependencies = [ + "async-trait", + "mempool_infra", + "starknet_api 0.12.0-dev.1 (git+https://github.com/starkware-libs/starknet-api.git?branch=main-mempool)", "thiserror", "tokio", ] +[[package]] +name = "starknet_sierra_compile" +version = "0.4.0-dev.2" +dependencies = [ + "assert_matches", + "cairo-lang-sierra", + "cairo-lang-starknet-classes", + "cairo-lang-utils", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "starknet_task_executor" +version = "0.4.0-dev.2" +dependencies = [ + "futures", + "rstest", + "tokio", + "tokio-test", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -8897,6 +9186,12 @@ dependencies = [ "xattr", ] +[[package]] +name = "target-lexicon" +version = "0.12.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" + [[package]] name = "tempfile" version = "3.10.1" @@ -9004,27 +9299,6 @@ dependencies = [ "which", ] -[[package]] -name = "test_utils" -version = "0.4.0-dev.2" -dependencies = [ - "cairo-lang-casm", - "cairo-lang-starknet-classes", - "cairo-lang-utils", - "indexmap 2.2.6", - "num-bigint 0.4.5", - "pretty_assertions", - "primitive-types", - "prometheus-parse", - "rand 0.8.5", - "rand_chacha 0.3.1", - "reqwest", - "serde", - "serde_json", - "starknet_api 0.12.0-dev.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tracing", -] - [[package]] name = "textwrap" version = "0.11.0" @@ -9170,6 +9444,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.2.0" @@ -9208,7 +9492,7 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.12", "tokio", ] @@ -9223,6 +9507,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -9231,11 +9528,11 @@ checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.21.12", "tokio", "tokio-rustls", "tungstenite", - "webpki-roots", + "webpki-roots 0.25.4", ] [[package]] @@ -9308,6 +9605,46 @@ dependencies = [ "winnow 0.6.8", ] +[[package]] +name = "tonic" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76c4eb7a4e9ef9d4763600161f12f5070b92a578e1b634db88a6887844c91a13" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-build" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4ef6dd70a610078cb4e338a0f79d06bc759ff1b22d2120c2ff02ae264ba9c2" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build", + "quote", + "syn 2.0.61", +] + [[package]] name = "tower" version = "0.4.13" @@ -9432,7 +9769,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "rustls", + "rustls 0.21.12", "sha1", "thiserror", "url", @@ -9511,6 +9848,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + [[package]] name = "universal-hash" version = "0.5.1" @@ -9549,6 +9892,22 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +dependencies = [ + "base64 0.22.1", + "log", + "once_cell", + "rustls 0.22.4", + "rustls-pki-types", + "rustls-webpki 0.102.4", + "url", + "webpki-roots 0.26.1", +] + [[package]] name = "url" version = "2.5.0" @@ -9770,6 +10129,15 @@ version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "which" version = "4.4.2" diff --git a/Cargo.toml b/Cargo.toml index 090dda507e5..0cee2a24a88 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ members = [ "crates/mempool", "crates/mempool_infra", "crates/mempool_node", + "crates/mempool_test_utils", + "crates/mempool_types", + "crates/native_blockifier", "crates/papyrus_base_layer", "crates/papyrus_common", "crates/papyrus_config", @@ -19,12 +22,18 @@ members = [ "crates/papyrus_node", "crates/papyrus_p2p_sync", "crates/papyrus_proc_macros", + "crates/papyrus_protobuf", "crates/papyrus_rpc", "crates/papyrus_storage", "crates/papyrus_sync", + "crates/papyrus_test_utils", + "crates/sequencing/papyrus_block_builder", + "crates/sequencing/papyrus_consensus", + "crates/starknet_api", "crates/starknet_client", - "crates/starknet-api", - "crates/test_utils", + "crates/starknet_sierra_compile", + "crates/task_executor", + "crates/tests-integration", ] [workspace.package] @@ -46,6 +55,7 @@ cached = "0.44.0" cairo-felt = "0.9.1" cairo-lang-casm = "2.6.0" cairo-lang-runner = "2.6.0" +cairo-lang-sierra = "2.6.0" cairo-lang-starknet-classes = "2.6.0" cairo-lang-utils = "2.6.0" cairo-vm = "0.9.2" @@ -71,7 +81,6 @@ tempfile = "3.7.0" thiserror = "1.0.37" clap = "4.3.10" const_format = "0.2.30" -papyrus_config = "0.3.0" pretty_assertions = "1.4.0" tower = "0.4.13" url = "2.5.0" @@ -81,7 +90,6 @@ assert-json-diff = "2.0.2" async-stream = "0.3.3" base64 = "0.13.0" bitvec = "1.0.1" -blockifier = "0.7.0-dev.1" bytes = "1" byteorder = "1.4.3" camelpaste = "0.1.0" @@ -119,8 +127,8 @@ os_info = "3.6.0" page_size = "0.6.0" # fixating the version of parity-scale-codec and parity-scale-codec-derive due to an error in udeps. # TODO: Remove this once udeps is fixed. -parity-scale-codec = "=3.6.5" -parity-scale-codec-derive = "=3.6.5" +parity-scale-codec = "=3.6.9" +parity-scale-codec-derive = "=3.6.9" paste = "1.0.9" primitive-types = "0.12.1" prometheus-parse = "0.2.4" @@ -130,7 +138,7 @@ prost-types = "0.12.1" rand = "0.8.5" rand_chacha = "0.3.1" replace_with = "0.1.7" -reqwest = "0.11" +reqwest = { version = "0.11", features = ["blocking", "json"] } rustc-hex = "2.1.0" schemars = "0.8.12" serde_repr = "0.1" @@ -144,14 +152,14 @@ strum = "0.25.0" strum_macros = "0.25.2" test-case = "3.2.1" test-log = "0.2.14" -tokio = "1.18.2" +tokio = { version = "1.37.0", features = ["full"] } tokio-retry = "0.3" tokio-stream = "0.1.8" +tokio-test = "0.4.4" tracing = "0.1.37" tracing-subscriber = "0.3.16" unsigned-varint = "0.8.0" void = "1.0.2" -starknet_api = "0.12.0-dev.1" [workspace.lints.rust] warnings = "deny" diff --git a/_blockifier_old b/_blockifier_old deleted file mode 160000 index 605118e9b76..00000000000 --- a/_blockifier_old +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 605118e9b76949719a67f5f259032bcce39d2b70 diff --git a/_mempool_old b/_mempool_old deleted file mode 160000 index cfe3fd4b85d..00000000000 --- a/_mempool_old +++ /dev/null @@ -1 +0,0 @@ -Subproject commit cfe3fd4b85d988d45a0f8f234414686e21011b4e diff --git a/_papyrus_old b/_papyrus_old deleted file mode 160000 index d224d5951cf..00000000000 --- a/_papyrus_old +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d224d5951cf3bd9fb45def82e59a624683a42f32 diff --git a/crates/blockifier/Cargo.toml b/crates/blockifier/Cargo.toml index 168b4530964..661697670b1 100644 --- a/crates/blockifier/Cargo.toml +++ b/crates/blockifier/Cargo.toml @@ -44,7 +44,6 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } sha3.workspace = true starknet-crypto.workspace = true -# TODO: bump to use latest version. starknet_api = { version = "0.12.0-dev.0", features = ["testing"] } strum.workspace = true strum_macros.workspace = true diff --git a/crates/blockifier/bench/blockifier_bench.rs b/crates/blockifier/bench/blockifier_bench.rs index 4400a00a229..0a04c956990 100644 --- a/crates/blockifier/bench/blockifier_bench.rs +++ b/crates/blockifier/bench/blockifier_bench.rs @@ -8,16 +8,19 @@ //! Run the benchmarks using `cargo bench --bench blockifier_bench`. use blockifier::abi::abi_utils::selector_from_name; -use blockifier::context::BlockContext; +use blockifier::blockifier::config::TransactionExecutorConfig; +use blockifier::blockifier::transaction_executor::TransactionExecutor; +use blockifier::bouncer::BouncerConfig; +use blockifier::context::{BlockContext, ChainInfo}; use blockifier::invoke_tx_args; -use blockifier::state::cached_state::CachedState; use blockifier::test_utils::contracts::FeatureContract; use blockifier::test_utils::dict_state_reader::DictStateReader; use blockifier::test_utils::initial_test_state::test_state; use blockifier::test_utils::invoke::invoke_tx; use blockifier::test_utils::{CairoVersion, NonceManager, BALANCE, MAX_FEE}; use blockifier::transaction::account_transaction::AccountTransaction; -use blockifier::transaction::transactions::ExecutableTransaction; +use blockifier::transaction::constants::TRANSFER_ENTRY_POINT_NAME; +use blockifier::transaction::transaction_execution::Transaction; use criterion::{criterion_group, criterion_main, Criterion}; use rand::{Rng, SeedableRng}; use starknet_api::core::ContractAddress; @@ -26,63 +29,85 @@ use starknet_api::transaction::{Calldata, Fee, TransactionVersion}; use starknet_api::{calldata, stark_felt}; const N_ACCOUNTS: u16 = 10000; +const CHUNK_SIZE: usize = 10; const RANDOMIZATION_SEED: u64 = 0; const CHARGE_FEE: bool = false; -const RUN_VALIDATION: bool = false; const TRANSACTION_VERSION: TransactionVersion = TransactionVersion(StarkFelt::ONE); pub fn transfers_benchmark(c: &mut Criterion) { - let account_contract = FeatureContract::AccountWithLongValidate(CairoVersion::Cairo0); - let block_context = &BlockContext::create_for_account_testing(); - let mut state = - test_state(block_context.chain_info(), BALANCE * 1000, &[(account_contract, N_ACCOUNTS)]); + let account_contract = FeatureContract::AccountWithoutValidations(CairoVersion::Cairo0); + let block_context = BlockContext::create_for_account_testing(); + let chain_info = &block_context.chain_info().clone(); + let state = test_state(chain_info, BALANCE * 1000, &[(account_contract, N_ACCOUNTS)]); + // TODO(Avi, 20/05/2024): Enable concurrency. + let executor_config = TransactionExecutorConfig::default(); + let executor = + &mut TransactionExecutor::new(state, block_context, BouncerConfig::max(), executor_config); let accounts = (0..N_ACCOUNTS) .map(|instance_id| account_contract.get_instance_address(instance_id)) .collect::>(); let nonce_manager = &mut NonceManager::default(); - let mut sender_account = 0; + let mut first_sender_index = 0; let mut random_generator = rand::rngs::StdRng::seed_from_u64(RANDOMIZATION_SEED); - let mut recipient_account = random_generator.gen::() % accounts.len(); // Create a benchmark group called "transfers", which iterates over the accounts round-robin // and performs transfers. c.bench_function("transfers", |benchmark| { benchmark.iter(|| { - do_transfer( - sender_account, - recipient_account, + execute_chunk_of_transfers( + first_sender_index, + &mut random_generator, &accounts, nonce_manager, - block_context, - &mut state, + chain_info, + executor, ); - sender_account = (sender_account + 1) % accounts.len(); - recipient_account = random_generator.gen::() % accounts.len(); + first_sender_index = (first_sender_index + CHUNK_SIZE) % accounts.len(); }) }); } -fn do_transfer( - sender_account: usize, - recipient_account: usize, +fn execute_chunk_of_transfers( + first_sender_index: usize, + random_generator: &mut rand::rngs::StdRng, accounts: &[ContractAddress], nonce_manager: &mut NonceManager, - block_context: &BlockContext, - state: &mut CachedState, + chain_info: &ChainInfo, + executor: &mut TransactionExecutor, ) { - let sender_address = accounts[sender_account]; - let recipient_account_address = accounts[recipient_account]; + let mut chunk: Vec = Vec::with_capacity(CHUNK_SIZE); + let mut sender_index = first_sender_index; + for _ in 0..CHUNK_SIZE { + let recipient_index = random_generator.gen::() % accounts.len(); + let account_tx = + generate_transfer(accounts, sender_index, recipient_index, nonce_manager, chain_info); + chunk.push(Transaction::AccountTransaction(account_tx)); + sender_index = (sender_index + 1) % accounts.len(); + } + let results = executor.execute_txs(&chunk, CHARGE_FEE); + assert_eq!(results.len(), CHUNK_SIZE); + for result in results { + assert!(!result.unwrap().is_reverted()); + } + // TODO(Avi, 01/06/2024): Run the same transactions concurrently on a new state and compare the + // state diffs. +} + +fn generate_transfer( + accounts: &[ContractAddress], + sender_index: usize, + recipient_index: usize, + nonce_manager: &mut NonceManager, + chain_info: &ChainInfo, +) -> AccountTransaction { + let sender_address = accounts[sender_index]; + let recipient_account_address = accounts[recipient_index]; let nonce = nonce_manager.next(sender_address); - let entry_point_selector = - selector_from_name(blockifier::transaction::constants::TRANSFER_ENTRY_POINT_NAME); + let entry_point_selector = selector_from_name(TRANSFER_ENTRY_POINT_NAME); let contract_address = match TRANSACTION_VERSION { - TransactionVersion::ONE => { - *block_context.chain_info().fee_token_addresses.eth_fee_token_address.0.key() - } - TransactionVersion::THREE => { - *block_context.chain_info().fee_token_addresses.strk_fee_token_address.0.key() - } + TransactionVersion::ONE => *chain_info.fee_token_addresses.eth_fee_token_address.0.key(), + TransactionVersion::THREE => *chain_info.fee_token_addresses.strk_fee_token_address.0.key(), _ => panic!("Unsupported transaction version: {TRANSACTION_VERSION:?}"), }; @@ -102,10 +127,7 @@ fn do_transfer( version: TRANSACTION_VERSION, nonce, }); - let account_tx = AccountTransaction::Invoke(tx); - let charge_fee = CHARGE_FEE; - let validate = RUN_VALIDATION; - account_tx.execute(state, block_context, charge_fee, validate).unwrap(); + AccountTransaction::Invoke(tx) } criterion_group!(benches, transfers_benchmark); diff --git a/crates/blockifier/feature_contracts/cairo1/account_faulty.cairo b/crates/blockifier/feature_contracts/cairo1/account_faulty.cairo index a3ee668ea60..3c08b1535f2 100644 --- a/crates/blockifier/feature_contracts/cairo1/account_faulty.cairo +++ b/crates/blockifier/feature_contracts/cairo1/account_faulty.cairo @@ -1,6 +1,6 @@ use core::option::OptionTrait; use core::traits::TryInto; -#[starknet::contract] +#[starknet::contract(account)] // A dummy account contract with faulty validations. @@ -69,7 +69,7 @@ mod Account { send_message_to_l1_syscall( to_address: to_address, payload: calldata.span() - ); + ).unwrap_syscall(); faulty_validate() } @@ -85,7 +85,7 @@ mod Account { send_message_to_l1_syscall( to_address: to_address, payload: calldata.span() - ); + ).unwrap_syscall(); starknet::VALIDATED } diff --git a/crates/blockifier/feature_contracts/cairo1/account_with_long_validate.cairo b/crates/blockifier/feature_contracts/cairo1/account_with_long_validate.cairo index b9f7cc0eeeb..1b4e267ec2a 100644 --- a/crates/blockifier/feature_contracts/cairo1/account_with_long_validate.cairo +++ b/crates/blockifier/feature_contracts/cairo1/account_with_long_validate.cairo @@ -1,4 +1,4 @@ -#[starknet::contract] +#[starknet::contract(account)] mod Account { use array::{ArrayTrait, SpanTrait}; use starknet::{ContractAddress, call_contract_syscall}; diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/account_faulty.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/account_faulty.casm.json index 4e9ead30cbc..8d2abe5ec2b 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/account_faulty.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/account_faulty.casm.json @@ -1,6 +1,6 @@ { "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "compiler_version": "2.4.0", + "compiler_version": "2.6.0", "bytecode": [ "0xa0680017fff8000", "0x7", @@ -8,21 +8,34 @@ "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0x6f", + "0x79", "0x4825800180007ffa", "0x0", "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x374", - "0x482680017ff98000", + "0x480680017fff8000", "0x1", - "0x20680017fff7ffd", - "0x56", - "0x48307ffb80007ffc", - "0x4824800180007fff", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0x51", + "0x48307ffc80007ffd", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -32,8 +45,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127ffb7fff8000", - "0x48127fea7fff8000", + "0x48127ff77fff8000", + "0x48127ff57fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -42,44 +55,41 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x786", + "0x6ec", "0x482480017fff8000", - "0x785", + "0x6eb", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fe8", - "0x8354", + "0x4824800180007ff3", + "0x5730", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007ff67fff", + "0x400080007ff27fff", "0x10780017fff7fff", - "0x24", - "0x4824800180007fe8", - "0x8354", - "0x400080007ff77fff", - "0x482480017ff78000", + "0x21", + "0x4824800180007ff3", + "0x5730", + "0x400080007ff37fff", + "0x482480017ff38000", "0x1", "0x48127ffe7fff8000", "0x480a7ffb7fff8000", "0x1104800180018000", - "0x368", + "0x4a1", "0x20680017fff7ffd", - "0x11", + "0xe", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x513", - "0x48127ff27fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", + "0x400080007fff7ffe", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", "0x480680017fff8000", "0x0", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x48127ffb7fff8000", + "0x482480017ffa8000", + "0x1", "0x208b7fff7fff7ffe", "0x48127ffa7fff8000", "0x48127ffa7fff8000", @@ -94,9 +104,9 @@ "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017ff48000", + "0x482480017ff08000", "0x1", - "0x48127fe37fff8000", + "0x48127fee7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -109,8 +119,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ffd7fff8000", - "0x48127fec7fff8000", + "0x48127ff87fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -139,33 +149,85 @@ "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0x9a", + "0xe5", "0x4825800180007ffa", "0x0", "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x2f1", - "0x482680017ff98000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xbd", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", "0x1", - "0x20680017fff7ffd", - "0x81", "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x480080007ff88000", + "0x10780017fff7fff", + "0x8", "0x48127ffb7fff8000", - "0x1104800180018000", - "0x2e9", - "0x20680017fff7ffe", - "0x6d", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x4c0", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffe", - "0x59", + "0x9a", "0x48307ffc80007ffd", - "0x4824800180007fff", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", + "0x1", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x48127ff87fff8000", + "0x10780017fff7fff", + "0x8", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0x77", + "0x480080007fff8000", + "0x20680017fff7fff", + "0x6", + "0x480680017fff8000", + "0x1", + "0x10780017fff7fff", + "0x4", + "0x480680017fff8000", "0x0", + "0x48307ffa80007ffb", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -175,8 +237,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127fd97fff8000", - "0x48127fc87fff8000", + "0x48127feb7fff8000", + "0x48127fe97fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -185,47 +247,70 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x6f7", + "0x62c", "0x482480017fff8000", - "0x6f6", + "0x62b", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fc6", - "0x9cb8", + "0x4824800180007fe7", + "0x6414", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007fd47fff", + "0x400080007fe67fff", "0x10780017fff7fff", - "0x27", - "0x4824800180007fc6", - "0x9cb8", - "0x400080007fd57fff", - "0x482480017fd58000", + "0x3e", + "0x4824800180007fe7", + "0x6414", + "0x400080007fe77fff", + "0x480680017fff8000", + "0x1", + "0x48307ff780007fff", + "0x482480017fe58000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffd", + "0x7", + "0x480680017fff8000", + "0x1", + "0x48307ffe80007fff", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", "0x1", "0x48127ffe7fff8000", + "0x20680017fff7fff", + "0x9", + "0x48127ffc7fff8000", + "0x48127ff87fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x56414c4944", + "0x10780017fff7fff", + "0xd", + "0x48127ffc7fff8000", + "0x48127ff87fff8000", "0x480a7ffb7fff8000", - "0x48127fd17fff8000", - "0x48127fe07fff8000", - "0x48127ff27fff8000", "0x1104800180018000", - "0x4c2", + "0x3c8", "0x20680017fff7ffd", - "0x11", + "0x12", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffc7fff8000", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x481", - "0x48127ff27fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", + "0x400080007fff7ffe", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", "0x480680017fff8000", "0x0", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x48127ffb7fff8000", + "0x482480017ffa8000", + "0x1", "0x208b7fff7fff7ffe", "0x48127ffa7fff8000", "0x48127ffa7fff8000", @@ -240,9 +325,9 @@ "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017fd28000", + "0x482480017fe48000", "0x1", - "0x48127fc17fff8000", + "0x48127fe27fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -255,8 +340,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202333", "0x400080007ffe7fff", - "0x48127fdb7fff8000", - "0x48127fca7fff8000", + "0x48127fee7fff8000", + "0x48127fec7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -269,8 +354,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202332", "0x400080007ffe7fff", - "0x48127fee7fff8000", - "0x48127fdd7fff8000", + "0x48127ff37fff8000", + "0x48127ff17fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -283,8 +368,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ffd7fff8000", - "0x48127fec7fff8000", + "0x48127ff87fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -307,146 +392,127 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x2", "0xa0680017fff8000", "0x7", "0x482680017ffa8000", - "0xffffffffffffffffffffffffffffed90", + "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0xa9", + "0x123", "0x4825800180007ffa", - "0x1270", + "0x0", "0x400280007ff97fff", "0x482680017ff98000", "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x481", - "0x20680017fff7ffe", - "0x90", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x239", - "0x40137ff07fff8000", - "0x20680017fff7ffe", - "0x7b", - "0x48127fec7fff8000", - "0x48127fd07fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x40137ffb7fff8001", - "0x1104800180018000", - "0x4aa", - "0x20680017fff7ffa", - "0x6a", - "0x20680017fff7ffd", - "0x5a", - "0x48307ffb80007ffc", - "0x4824800180007fff", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0xf8", + "0xa0680017fff8004", + "0xe", + "0x4824800180047ffe", + "0x800000000000000000000000000000000000000000000000000000000000000", + "0x484480017ffe8000", + "0x110000000000000000", + "0x48307ffe7fff8002", + "0x480080007ff67ffc", + "0x480080017ff57ffc", + "0x402480017ffb7ffd", + "0xffffffffffffffeeffffffffffffffff", + "0x400080027ff47ffd", + "0x10780017fff7fff", + "0xe6", + "0x484480017fff8001", + "0x8000000000000000000000000000000", + "0x48307fff80007ffd", + "0x480080007ff77ffd", + "0x480080017ff67ffd", + "0x402480017ffc7ffe", + "0xf8000000000000000000000000000000", + "0x400080027ff57ffe", + "0x482480017ff58000", + "0x3", + "0x48307ff680007ff7", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0x10", - "0x40780017fff7fff", + "0xa", + "0x482480017ff58000", "0x1", + "0x48127ff57fff8000", "0x480680017fff8000", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x400080007ffe7fff", - "0x48127ff47fff8000", - "0x48127ff47fff8000", - "0x480a7ffb7fff8000", + "0x0", + "0x480080007ff28000", + "0x10780017fff7fff", + "0x8", + "0x48127ff57fff8000", + "0x48127ff57fff8000", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x641", - "0x482480017fff8000", - "0x640", - "0x480080007fff8000", - "0xa0680017fff8000", - "0x9", - "0x4824800180007ff2", - "0xcb0c", - "0x482480017fff8000", - "0x100000000000000000000000000000000", - "0x400080007fef7fff", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xb9", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", "0x10780017fff7fff", - "0x28", - "0x4824800180007ff2", - "0xcb0c", - "0x400080007ff07fff", - "0x482480017ff08000", - "0x1", - "0x48127ffe7fff8000", - "0x480a7ffb7fff8000", - "0x480a80007fff8000", - "0x480a80017fff8000", - "0x48127ff17fff8000", - "0x48127ff17fff8000", - "0x1104800180018000", - "0x4be", - "0x20680017fff7ffd", - "0x11", - "0x40780017fff7fff", + "0xa", + "0x482480017ffb8000", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x3ca", - "0x48127ff27fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", + "0x48127ffb7fff8000", "0x480680017fff8000", "0x0", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", + "0x48127ff87fff8000", + "0x10780017fff7fff", + "0x8", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482480017fed8000", "0x1", - "0x48127fed7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", + "0x0", + "0x20680017fff7ffe", + "0x20", "0x40780017fff7fff", "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202333", - "0x400080007ffe7fff", - "0x48127ff67fff8000", - "0x48127ff67fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", + "0x48127ff47fff8000", + "0x48127fe77fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ffb7fff8000", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", + "0x480080007ff88000", + "0x1104800180018000", + "0x4d2", + "0x20680017fff7ffa", + "0xb", + "0x48127ff87fff8000", + "0x48127ff87fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x10780017fff7fff", + "0x14", "0x48127ff87fff8000", "0x48127ff87fff8000", "0x480a7ffb7fff8000", @@ -455,90 +521,19 @@ "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202332", - "0x400080007ffe7fff", - "0x48127fea7fff8000", - "0x48127fce7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", + "0x48127ff57fff8000", + "0x48127fe87fff8000", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202331", - "0x400080007ffe7fff", - "0x48127ff97fff8000", - "0x48127fdd7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482680017ff98000", "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x2", - "0xa0680017fff8000", - "0x7", - "0x482680017ffa8000", - "0xffffffffffffffffffffffffffffed90", - "0x400280007ff97fff", - "0x10780017fff7fff", - "0x9e", - "0x4825800180007ffa", - "0x1270", - "0x400280007ff97fff", - "0x482680017ff98000", - "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x3c2", - "0x20680017fff7ffe", - "0x85", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x17a", - "0x40137ff07fff8000", - "0x20680017fff7ffe", - "0x70", - "0x48127fec7fff8000", - "0x48127fd07fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x40137ffb7fff8001", - "0x1104800180018000", - "0x3eb", - "0x20680017fff7ffa", - "0x5f", + "0x0", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffd", - "0x4f", + "0x6c", "0x48307ffb80007ffc", - "0x4824800180007fff", - "0x0", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -548,8 +543,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127ff47fff8000", - "0x48127ff47fff8000", + "0x48127ff67fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -558,43 +553,74 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x582", + "0x4fa", "0x482480017fff8000", - "0x581", + "0x4f9", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007ff2", - "0x319c", + "0x4824800180007ff4", + "0x9bdc", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007fef7fff", + "0x400080007ff17fff", "0x10780017fff7fff", - "0x1d", - "0x4824800180007ff2", - "0x319c", - "0x400080007ff07fff", - "0x48127fff7fff8000", - "0x480a7ffb7fff8000", - "0x480a80007fff8000", - "0x480a80017fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", - "0x1104800180018000", - "0x42f", - "0x40780017fff7fff", + "0x3c", + "0x4824800180007ff4", + "0x9bdc", + "0x400080007ff27fff", + "0x480680017fff8000", + "0x0", + "0x482480017ff18000", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", + "0x480680017fff8000", + "0x53656e644d657373616765546f4c31", + "0x400280007ffb7fff", + "0x400280017ffb7ffc", + "0x400280027ffb7ffd", + "0x400280037ffb7ff4", + "0x400280047ffb7ff5", + "0x480280067ffb8000", + "0x20680017fff7fff", + "0x1d", "0x48127ffd7fff8000", + "0x480280057ffb8000", + "0x482680017ffb8000", + "0x7", "0x1104800180018000", - "0x30f", - "0x482480017fd88000", + "0x2a1", + "0x20680017fff7ffd", + "0xe", + "0x40780017fff7fff", "0x1", - "0x48127ff47fff8000", - "0x48127ff47fff8000", + "0x400080007fff7ffe", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", "0x480680017fff8000", "0x0", + "0x48127ffb7fff8000", + "0x482480017ffa8000", + "0x1", + "0x208b7fff7fff7ffe", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x10780017fff7fff", + "0x8", + "0x48127ffd7fff8000", + "0x480280057ffb8000", + "0x482680017ffb8000", + "0x9", + "0x480280077ffb8000", + "0x480280087ffb8000", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", @@ -603,9 +629,9 @@ "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017fed8000", + "0x482480017fef8000", "0x1", - "0x48127fed7fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -618,8 +644,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202333", "0x400080007ffe7fff", - "0x48127ff67fff8000", - "0x48127ff67fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -627,21 +653,13 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202332", "0x400080007ffe7fff", - "0x48127fea7fff8000", - "0x48127fce7fff8000", + "0x48127ff87fff8000", + "0x48127feb7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -649,13 +667,20 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x482480017ff48000", + "0x3", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", + "0x6", + "0x48127ff47fff8000", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ff97fff8000", - "0x48127fdd7fff8000", + "0x48127ffd7fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -684,114 +709,142 @@ "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0x49", + "0x112", "0x4825800180007ffa", "0x0", "0x400280007ff97fff", - "0x48297ffc80007ffd", "0x482680017ff98000", "0x1", - "0x4824800180007ffe", - "0x0", + "0x48297ffc80007ffd", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0x10", - "0x40780017fff7fff", + "0xa", + "0x482680017ffc8000", "0x1", + "0x480a7ffd7fff8000", "0x480680017fff8000", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x400080007ffe7fff", - "0x48127ffc7fff8000", - "0x48127ff97fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x4e8", - "0x482480017fff8000", - "0x4e7", - "0x480080007fff8000", - "0xa0680017fff8000", - "0x9", - "0x4824800180007ff7", "0x0", - "0x482480017fff8000", - "0x100000000000000000000000000000000", - "0x400080007ff77fff", + "0x480280007ffc8000", "0x10780017fff7fff", - "0x12", - "0x4824800180007ff7", + "0x8", + "0x480a7ffc7fff8000", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", "0x0", - "0x400080007ff87fff", - "0x1104800180018000", - "0x3b4", - "0x40780017fff7fff", - "0x1", + "0x20680017fff7ffe", + "0xe7", + "0xa0680017fff8004", + "0xe", + "0x4824800180047ffe", + "0x800000000000000000000000000000000000000000000000000000000000000", + "0x484480017ffe8000", + "0x110000000000000000", + "0x48307ffe7fff8002", + "0x480080007ff67ffc", + "0x480080017ff57ffc", + "0x402480017ffb7ffd", + "0xffffffffffffffeeffffffffffffffff", + "0x400080027ff47ffd", + "0x10780017fff7fff", + "0xd5", + "0x484480017fff8001", + "0x8000000000000000000000000000000", + "0x48307fff80007ffd", + "0x480080007ff77ffd", + "0x480080017ff67ffd", + "0x402480017ffc7ffe", + "0xf8000000000000000000000000000000", + "0x400080027ff57ffe", + "0x482480017ff58000", + "0x3", + "0x48307ff680007ff7", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", "0x482480017ff58000", "0x1", + "0x48127ff57fff8000", + "0x480680017fff8000", + "0x0", + "0x480080007ff28000", + "0x10780017fff7fff", + "0x8", + "0x48127ff57fff8000", + "0x48127ff57fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xa8", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", + "0x1", "0x48127ffb7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", "0x0", + "0x48127ff87fff8000", + "0x10780017fff7fff", + "0x8", "0x48127ffb7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", + "0x48127ffb7fff8000", + "0x480680017fff8000", "0x1", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482480017ff58000", + "0x0", + "0x20680017fff7ffe", + "0x20", + "0x40780017fff7fff", "0x1", - "0x48127ff27fff8000", + "0x48127ff47fff8000", + "0x48127fe77fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ffb7fff8000", + "0x48127ffa7fff8000", + "0x480080007ff88000", + "0x1104800180018000", + "0x39b", + "0x20680017fff7ffa", + "0xb", + "0x48127ff87fff8000", + "0x48127ff87fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x10780017fff7fff", + "0x14", + "0x48127ff87fff8000", + "0x48127ff87fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", + "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", + "0x48127ff57fff8000", + "0x48127fe87fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482680017ff98000", "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0xa0680017fff8000", - "0x7", - "0x482680017ffa8000", - "0x100000000000000000000000000000000", - "0x400280007ff97fff", - "0x10780017fff7fff", - "0x6b", - "0x4825800180007ffa", "0x0", - "0x400280007ff97fff", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x250", - "0x482680017ff98000", - "0x1", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffd", - "0x52", + "0x5b", "0x48307ffb80007ffc", - "0x4824800180007fff", - "0x0", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -801,8 +854,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127ffb7fff8000", - "0x48127fe67fff8000", + "0x48127ff67fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -811,57 +864,96 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x485", + "0x3c3", "0x482480017fff8000", - "0x484", + "0x3c2", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fe4", - "0x89f8", + "0x4824800180007ff4", + "0x28b4", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007ff67fff", + "0x400080007ff17fff", "0x10780017fff7fff", - "0x20", - "0x4824800180007fe4", - "0x89f8", - "0x400080007ff77fff", - "0x482480017ff78000", + "0x2b", + "0x4824800180007ff4", + "0x28b4", + "0x400080007ff27fff", + "0x480680017fff8000", + "0x0", + "0x482480017ff18000", "0x1", - "0x48127ffe7fff8000", - "0x480a7ffb7fff8000", - "0x48127ff37fff8000", - "0x1104800180018000", - "0x34d", - "0x20680017fff7ffd", - "0xc", + "0x480680017fff8000", + "0x53656e644d657373616765546f4c31", + "0x400280007ffb7fff", + "0x400280017ffb7ffc", + "0x400280027ffb7ffd", + "0x400280037ffb7ff4", + "0x400280047ffb7ff5", + "0x480280067ffb8000", + "0x20680017fff7fff", + "0x11", "0x40780017fff7fff", "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", "0x480680017fff8000", - "0x0", + "0x56414c4944", + "0x400080007ffe7fff", "0x48127ffb7fff8000", + "0x480280057ffb8000", + "0x482680017ffb8000", + "0x7", + "0x480680017fff8000", + "0x0", "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x48127ffd7fff8000", + "0x480280057ffb8000", + "0x482680017ffb8000", + "0x9", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x480280077ffb8000", + "0x480280087ffb8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017ff48000", + "0x482480017fef8000", + "0x1", + "0x48127fef7fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4661696c656420746f20646573657269616c697a6520706172616d202333", + "0x400080007ffe7fff", + "0x48127ff77fff8000", + "0x48127ff77fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", "0x1", - "0x48127fdf7fff8000", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4661696c656420746f20646573657269616c697a6520706172616d202332", + "0x400080007ffe7fff", + "0x48127ff87fff8000", + "0x48127feb7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -869,13 +961,20 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x482480017ff48000", + "0x3", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", + "0x6", + "0x48127ff47fff8000", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", "0x48127ffd7fff8000", - "0x48127fe87fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -898,380 +997,234 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0xa0680017fff8000", + "0x7", + "0x482680017ffa8000", + "0x100000000000000000000000000000000", + "0x400280007ff97fff", + "0x10780017fff7fff", + "0x45", + "0x4825800180007ffa", + "0x0", + "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", "0x48297ffc80007ffd", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0xa", - "0x482680017ffc8000", - "0x1", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0x8", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", + "0x10", + "0x40780017fff7fff", "0x1", "0x480680017fff8000", - "0x0", - "0x48127ffc7fff8000", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x400080007ffe7fff", "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x8", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x480680017fff8000", - "0x0", - "0x480080007ffa8000", - "0x208b7fff7fff7ffe", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", + "0x48127ffa7fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", - "0x480680017fff8000", - "0x0", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x313", - "0x20680017fff7ffd", - "0x1a6", - "0x480a7ffb7fff8000", - "0x480080037ffe8000", - "0x480080047ffd8000", - "0x480680017fff8000", - "0x0", "0x1104800180018000", - "0x31f", - "0x480080037ff08000", - "0x480080047fef8000", - "0x20680017fff7ffb", - "0x191", - "0x480080007ffd8000", - "0x4824800180007fff", + "0x31e", + "0x482480017fff8000", + "0x31d", + "0x480080007fff8000", + "0xa0680017fff8000", + "0x9", + "0x4824800180007ff8", "0x0", - "0x20680017fff7fff", - "0xe", - "0x40780017fff7fff", - "0x52", - "0x48127fa67fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", + "0x482480017fff8000", + "0x100000000000000000000000000000000", + "0x400080007ff77fff", + "0x10780017fff7fff", + "0x10", + "0x4824800180007ff8", "0x0", - "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x4824800180007ffe", - "0x1", - "0x20680017fff7fff", - "0x24", + "0x400080007ff87fff", "0x40780017fff7fff", - "0x4d", - "0x480680017fff8000", - "0x0", - "0x4824800180007fff", "0x1", - "0x20680017fff7fff", - "0xe", - "0x40780017fff7fff", - "0x2", - "0x48127fa67fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x480680017fff8000", - "0x0", + "0x482480017ff78000", + "0x1", + "0x48127ffd7fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x0", - "0x480680017fff8000", - "0x494e56414c4944", + "0x48127ffb7fff8000", + "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", - "0x496e76616c6964207363656e6172696f", + "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x48127fa67fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", + "0x482480017ff58000", + "0x1", + "0x48127ff37fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x4824800180007ffd", - "0x2", - "0x20680017fff7fff", - "0x78", "0x40780017fff7fff", - "0x2a", - "0x48127fcc7fff8000", - "0x48127fcf7fff8000", - "0x48127fcf7fff8000", + "0x1", "0x480680017fff8000", + "0x4f7574206f6620676173", + "0x400080007ffe7fff", + "0x482680017ff98000", "0x1", - "0x1104800180018000", - "0x2d7", - "0x20680017fff7ffd", - "0x63", - "0x40780017fff7fff", + "0x480a7ffa7fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", "0x1", - "0x480080007ffe8000", - "0xa0680017fff8004", - "0xe", - "0x4824800180047ffe", - "0x800000000000000000000000000000000000000000000000000000000000000", - "0x484480017ffe8000", - "0x110000000000000000", - "0x48307ffe7fff8002", - "0x480080007ff67ffc", - "0x480080017ff57ffc", - "0x402480017ffb7ffd", - "0xffffffffffffffeeffffffffffffffff", - "0x400080027ff47ffd", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0xa0680017fff8000", + "0x7", + "0x482680017ffa8000", + "0x100000000000000000000000000000000", + "0x400280007ff97fff", "0x10780017fff7fff", - "0x41", - "0x484480017fff8001", - "0x8000000000000000000000000000000", - "0x48307fff80007ffd", - "0x480080007ff77ffd", - "0x480080017ff67ffd", - "0x402480017ffc7ffe", - "0xf8000000000000000000000000000000", - "0x400080027ff57ffe", - "0x480680017fff8000", - "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", - "0x48127ff87fff8000", - "0x48127ff77fff8000", - "0x482480017ff28000", - "0x3", - "0x480680017fff8000", - "0x43616c6c436f6e7472616374", - "0x400080007fa27fff", - "0x400080017fa27fa1", - "0x400080027fa27ff5", - "0x400080037fa27ffb", - "0x400080047fa27ffc", - "0x400080057fa27ffd", - "0x480080077fa28000", + "0x9a", + "0x4825800180007ffa", + "0x0", + "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", "0x20680017fff7fff", - "0xb", - "0x480080067fa18000", - "0x482480017fa08000", + "0x4", + "0x10780017fff7fff", "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", "0x480680017fff8000", "0x0", - "0x480080087f9e8000", - "0x480080097f9d8000", + "0x480a7ffc7fff8000", "0x10780017fff7fff", - "0x9", - "0x480080067fa18000", - "0x482480017fa08000", - "0xa", + "0x8", + "0x480a7ffc7fff8000", + "0x480a7ffd7fff8000", "0x480680017fff8000", "0x1", - "0x480080087f9e8000", - "0x480080097f9d8000", - "0x1104800180018000", - "0x2bb", - "0x20680017fff7ffd", - "0xc", - "0x48127ff37fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x0", "0x480680017fff8000", "0x0", - "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x48127ff37fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", + "0x20680017fff7ffe", + "0x72", + "0x480080007fff8000", + "0x20680017fff7fff", + "0x6", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0xd", + "0x10780017fff7fff", + "0x4", + "0x480680017fff8000", + "0x0", + "0x48307ffa80007ffb", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0x10", "0x40780017fff7fff", "0x1", "0x480680017fff8000", - "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x482480017fe58000", - "0x3", - "0x48127f957fff8000", - "0x48127f957fff8000", + "0x48127ff57fff8000", + "0x48127ff37fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x17", - "0x48127fe57fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", + "0x1104800180018000", + "0x2a7", + "0x482480017fff8000", + "0x2a6", + "0x480080007fff8000", + "0xa0680017fff8000", + "0x9", + "0x4824800180007ff1", + "0x5d0c", + "0x482480017fff8000", + "0x100000000000000000000000000000000", + "0x400080007ff07fff", + "0x10780017fff7fff", + "0x39", + "0x4824800180007ff1", + "0x5d0c", + "0x400080007ff17fff", "0x480680017fff8000", "0x1", - "0x48127fe37fff8000", - "0x48127fe37fff8000", - "0x208b7fff7fff7ffe", - "0x4824800180007ffc", - "0x3", - "0x20680017fff7fff", - "0x35", - "0x40780017fff7fff", - "0x42", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x476574426c6f636b48617368", - "0x400080007fa27fff", - "0x400080017fa27fa1", - "0x400080027fa27ffe", - "0x480080047fa28000", - "0x20680017fff7fff", - "0xc", - "0x480080037fa18000", - "0x482480017fa08000", - "0x6", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480080057f9d8000", - "0x10780017fff7fff", - "0x9", - "0x480080037fa18000", - "0x482480017fa08000", - "0x7", + "0x48307ff780007fff", + "0x482480017fef8000", + "0x1", "0x480680017fff8000", "0x1", - "0x480080057f9e8000", - "0x480080067f9d8000", - "0x1104800180018000", - "0x275", "0x20680017fff7ffd", - "0xc", - "0x48127fa67fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x48127fa67fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", + "0x7", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x4824800180007ffb", - "0x4", - "0x20680017fff7fff", - "0x95", - "0x48127ff47fff8000", - "0x48127ff77fff8000", - "0x48127ff77fff8000", - "0x480680017fff8000", + "0x48307ffe80007fff", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", "0x1", - "0x1104800180018000", - "0x228", - "0x20680017fff7ffd", - "0x82", + "0x48127ffe7fff8000", + "0x20680017fff7fff", + "0x7", "0x48127ffc7fff8000", - "0x48127fe87fff8000", - "0x48127fe87fff8000", - "0x480680017fff8000", - "0x2", - "0x1104800180018000", - "0x21f", - "0x480080007ff08000", - "0x20680017fff7ffc", - "0x6e", - "0x48127ffb7fff8000", - "0x48127fd87fff8000", - "0x48127fd87fff8000", - "0x480680017fff8000", - "0x3", - "0x1104800180018000", - "0x215", - "0x480080007fef8000", - "0x20680017fff7ffc", - "0x5a", - "0x48127fb57fff8000", - "0x48127fb57fff8000", + "0x48127ff87fff8000", + "0x480a7ffb7fff8000", + "0x10780017fff7fff", + "0xc", + "0x48127ffc7fff8000", + "0x48127ff87fff8000", + "0x480a7ffb7fff8000", "0x1104800180018000", - "0x249", - "0x480080007fe98000", - "0x20680017fff7ffc", - "0x49", - "0x480080007ffe8000", - "0x480080007fff8000", - "0x48307fd780007fff", - "0x480080017ffd8000", - "0x480080027ffc8000", + "0x45", "0x20680017fff7ffd", - "0x32", - "0x48307fe480007ffe", - "0x20680017fff7fff", - "0x1f", - "0x48307ff980007ffe", - "0x20680017fff7fff", - "0xe", + "0xf", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", "0x40780017fff7fff", - "0x2", - "0x48127fdc7fff8000", - "0x48127ff07fff8000", - "0x48127ff07fff8000", - "0x480680017fff8000", - "0x0", + "0x1", + "0x48127ffc7fff8000", + "0x48127ffc7fff8000", + "0x48127ffc7fff8000", "0x480680017fff8000", "0x0", - "0x480680017fff8000", - "0x56414c4944", + "0x48127ffb7fff8000", + "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x53455155454e4345525f4d49534d41544348", - "0x400080007ffe7fff", - "0x48127fdc7fff8000", - "0x48127ff07fff8000", - "0x48127ff07fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", + "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", - "0x40780017fff7fff", - "0x1", "0x480680017fff8000", - "0x424c4f434b5f54494d455354414d505f4d49534d41544348", + "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x48127fdc7fff8000", - "0x48127ff07fff8000", - "0x48127ff07fff8000", + "0x482480017fee8000", + "0x1", + "0x48127fec7fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", @@ -1279,15 +1232,13 @@ "0x1", "0x208b7fff7fff7ffe", "0x40780017fff7fff", - "0x2", - "0x40780017fff7fff", "0x1", "0x480680017fff8000", - "0x424c4f434b5f4e554d4245525f4d49534d41544348", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127fdc7fff8000", - "0x48127ff07fff8000", - "0x48127ff07fff8000", + "0x48127ff87fff8000", + "0x48127ff67fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", @@ -1295,193 +1246,128 @@ "0x1", "0x208b7fff7fff7ffe", "0x40780017fff7fff", - "0x9", - "0x48127fdc7fff8000", - "0x48127ff07fff8000", - "0x48127ff07fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ff07fff8000", - "0x48127ff07fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1f", - "0x48127fdc7fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x480680017fff8000", - "0x1", - "0x48127fda7fff8000", - "0x48127fda7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x2f", - "0x48127fcc7fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x480680017fff8000", "0x1", - "0x48127fca7fff8000", - "0x48127fca7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x3f", - "0x48127fbd7fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", "0x480680017fff8000", - "0x1", - "0x48127fbb7fff8000", - "0x48127fbb7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x4c", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x556e6b6e6f776e207363656e6172696f", + "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x48127fa67fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", + "0x482680017ff98000", + "0x1", + "0x480a7ffa7fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x54", - "0x48127fa67fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", "0x480680017fff8000", - "0x1", - "0x48127fa47fff8000", - "0x48127fa47fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x65", - "0x480a7ffb7fff8000", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x480680017fff8000", - "0x1", - "0x48127f957fff8000", - "0x48127f957fff8000", - "0x208b7fff7fff7ffe", - "0x400380007ffd7ffb", - "0x480a7ffc7fff8000", - "0x482680017ffd8000", - "0x1", - "0x208b7fff7fff7ffe", - "0x48297ffc80007ffd", + "0x476574457865637574696f6e496e666f", + "0x400280007ffd7fff", + "0x400380017ffd7ffc", + "0x480280037ffd8000", "0x20680017fff7fff", - "0x4", - "0x10780017fff7fff", - "0xa", - "0x482680017ffc8000", - "0x1", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0x8", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x1", + "0x1ca", + "0x480280047ffd8000", + "0x480080017fff8000", + "0x480080037fff8000", + "0x480080047ffe8000", "0x480680017fff8000", "0x0", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x15", - "0x480080007ffd8000", - "0x4824800180007fff", - "0x0", - "0x20680017fff7fff", + "0x480280027ffd8000", + "0x482680017ffd8000", + "0x5", + "0x480080037ffa8000", + "0x480080047ff98000", + "0x48307ff980007ffa", + "0xa0680017fff8000", "0x6", - "0x480680017fff8000", - "0x1", + "0x48307ffe80007ff9", + "0x400280007ffb7fff", "0x10780017fff7fff", - "0x4", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", + "0x1a7", + "0x482480017ff98000", "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", - "0x0", - "0x48307ffb80007ffc", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x4", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", + "0x48307fff80007ffd", + "0x400280007ffb7fff", + "0x48307ff77ff58000", + "0x480080007fff8000", + "0x482680017ffb8000", "0x1", + "0x20680017fff7ffe", + "0xe", + "0x40780017fff7fff", + "0x28", + "0x48127fd77fff8000", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x0", - "0x208b7fff7fff7ffe", "0x480680017fff8000", "0x0", - "0x20780017fff7ffd", - "0x7", "0x480680017fff8000", + "0x56414c4944", + "0x208b7fff7fff7ffe", + "0x4824800180007ffe", "0x1", - "0x48307ffe80007fff", - "0x10780017fff7fff", - "0x5", + "0x20680017fff7fff", + "0x24", "0x40780017fff7fff", + "0x23", + "0x480680017fff8000", + "0x0", + "0x4824800180007fff", "0x1", - "0x48127ffe7fff8000", "0x20680017fff7fff", "0xe", "0x40780017fff7fff", - "0x8e", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x480a7ffa7fff8000", + "0x2", + "0x48127fd77fff8000", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x0", "0x480680017fff8000", "0x0", "0x480680017fff8000", - "0x56414c4944", + "0x494e56414c4944", "0x208b7fff7fff7ffe", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x480a7ffa7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffdf8", - "0x20680017fff7ffd", - "0xb", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", + "0x496e76616c6964207363656e6172696f", + "0x400080007ffe7fff", + "0x48127fd77fff8000", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", - "0x0", + "0x1", "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x4824800180007ffd", + "0x2", + "0x20680017fff7fff", + "0x70", + "0x40780017fff7fff", + "0x15", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffdbd", - "0x20680017fff7ffe", - "0x2b", + "0x48307fdf80007fe0", + "0xa0680017fff8000", + "0x6", + "0x48307ffe80007ffd", + "0x400080007fe47fff", + "0x10780017fff7fff", + "0x54", + "0x482480017ffd8000", + "0x1", + "0x48307fff80007ffd", + "0x400080007fe37fff", + "0x48307ffb7fdb8000", + "0x40780017fff7fff", + "0x1", + "0x480080007ffe8000", "0xa0680017fff8004", "0xe", "0x4824800180047ffe", @@ -1489,337 +1375,357 @@ "0x484480017ffe8000", "0x110000000000000000", "0x48307ffe7fff8002", - "0x480280007ffb7ffc", - "0x480280017ffb7ffc", + "0x480080017fdc7ffc", + "0x480080027fdb7ffc", "0x402480017ffb7ffd", "0xffffffffffffffeeffffffffffffffff", - "0x400280027ffb7ffd", + "0x400080037fda7ffd", "0x10780017fff7fff", - "0x14", + "0x2d", "0x484480017fff8001", "0x8000000000000000000000000000000", "0x48307fff80007ffd", - "0x480280007ffb7ffd", - "0x480280017ffb7ffd", + "0x480080017fdd7ffd", + "0x480080027fdc7ffd", "0x402480017ffc7ffe", "0xf8000000000000000000000000000000", - "0x400280027ffb7ffe", - "0x40780017fff7fff", - "0x1", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", + "0x400080037fdb7ffe", + "0x480680017fff8000", + "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", + "0x482480017fda8000", + "0x4", + "0x480680017fff8000", + "0x43616c6c436f6e7472616374", + "0x400080007fcf7fff", + "0x400080017fcf7fce", + "0x400080027fcf7ff7", + "0x400080037fcf7ffd", + "0x400080047fcf7ff6", + "0x400080057fcf7ff6", + "0x480080077fcf8000", + "0x20680017fff7fff", + "0xd", + "0x48127ffd7fff8000", + "0x480080067fcd8000", + "0x482480017fcc8000", + "0xa", "0x480680017fff8000", "0x0", - "0x48127ff57fff8000", + "0x480680017fff8000", + "0x0", + "0x480680017fff8000", + "0x56414c4944", "0x208b7fff7fff7ffe", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", + "0x48127ffd7fff8000", + "0x480080067fcd8000", + "0x482480017fcc8000", + "0xa", "0x480680017fff8000", "0x1", - "0x480680017fff8000", - "0x0", + "0x480080087fca8000", + "0x480080097fc98000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", - "0x6", - "0x480a7ffb7fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", + "0x1", + "0x40780017fff7fff", "0x1", "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48297ffc80007ffd", - "0x20680017fff7fff", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x482480017fd78000", "0x4", - "0x10780017fff7fff", - "0xa", - "0x482680017ffc8000", - "0x1", - "0x480a7ffd7fff8000", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0x8", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0xb", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", + "0x496e646578206f7574206f6620626f756e6473", + "0x400080007ffe7fff", + "0x482480017fd78000", "0x1", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", - "0x0", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x26", - "0x40780017fff7fff", "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", "0x48127ffa7fff8000", - "0x480080007ff68000", - "0x1104800180018000", - "0x119", - "0x20680017fff7ffa", - "0xc", - "0x48127ff87fff8000", - "0x48127ff87fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x4824800180007ffc", + "0x3", + "0x20680017fff7fff", + "0x22", + "0x40780017fff7fff", + "0x22", "0x480680017fff8000", "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff87fff8000", - "0x48127ff87fff8000", "0x480680017fff8000", - "0x1", + "0x476574426c6f636b48617368", + "0x400080007fcf7fff", + "0x400080017fcf7fce", + "0x400080027fcf7ffe", + "0x480080047fcf8000", + "0x20680017fff7fff", + "0xd", + "0x48127fd77fff8000", + "0x480080037fcd8000", + "0x482480017fcc8000", + "0x6", "0x480680017fff8000", "0x0", "0x480680017fff8000", "0x0", "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", + "0x56414c4944", "0x208b7fff7fff7ffe", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x48127fd77fff8000", + "0x480080037fcd8000", + "0x482480017fcc8000", + "0x7", "0x480680017fff8000", "0x1", + "0x480080057fca8000", + "0x480080067fc98000", + "0x208b7fff7fff7ffe", + "0x4824800180007ffb", + "0x4", + "0x20680017fff7fff", + "0xc1", "0x480680017fff8000", - "0x0", + "0x1", + "0x48307ff280007ff3", + "0xa0680017fff8000", + "0x6", + "0x48307ffe80007ffd", + "0x400080007ff77fff", + "0x10780017fff7fff", + "0xa7", + "0x482480017ffd8000", + "0x1", + "0x48307fff80007ffd", + "0x400080007ff67fff", + "0x48307ffb7fee8000", "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", + "0x2", + "0x480080007ffe8000", + "0x48307feb80007fec", + "0xa0680017fff8000", + "0x6", + "0x48307ffe80007ffc", + "0x400080017ff07fff", + "0x10780017fff7fff", + "0x87", + "0x482480017ffc8000", + "0x1", + "0x48307fff80007ffd", + "0x400080017fef7fff", + "0x48307ffa7fe78000", "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", + "0x3", + "0x480080007ffe8000", + "0x48307fe480007fe5", + "0xa0680017fff8000", + "0x6", + "0x48307ffe80007ffc", + "0x400080027fe97fff", + "0x10780017fff7fff", + "0x67", + "0x482480017ffc8000", + "0x1", + "0x48307fff80007ffd", + "0x400080027fe87fff", + "0x48307ffa7fe08000", + "0x482480017fe78000", + "0x3", + "0x480080007ffe8000", "0x480680017fff8000", - "0x53656e644d657373616765546f4c31", - "0x400280007ff97fff", - "0x400380017ff97ff8", - "0x400280027ff97ffc", - "0x400280037ff97ffd", - "0x400280047ff97ffe", - "0x480280067ff98000", + "0x476574457865637574696f6e496e666f", + "0x400080007fdb7fff", + "0x400080017fdb7fda", + "0x480080037fdb8000", "0x20680017fff7fff", - "0x7", - "0x480280057ff98000", - "0x482680017ff98000", - "0x7", - "0x10780017fff7fff", + "0x4d", + "0x480080047fda8000", + "0x480080007fff8000", + "0x480080007fff8000", + "0x48307fec80007fff", + "0x480080027fd68000", + "0x482480017fd58000", "0x5", - "0x480280057ff98000", - "0x482680017ff98000", - "0x9", - "0x480a7ff77fff8000", - "0x48127ffd7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffd49", - "0x20680017fff7ffd", - "0xb", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", - "0x0", + "0x480080017ffb8000", + "0x480080027ffa8000", + "0x20680017fff7ffb", + "0x32", + "0x48307fee80007ffe", + "0x20680017fff7fff", + "0x1f", + "0x48307ff480007ffe", + "0x20680017fff7fff", + "0xe", + "0x40780017fff7fff", + "0x2", + "0x48127ff07fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480680017fff8000", "0x0", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", "0x480680017fff8000", "0x0", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x53656e644d657373616765546f4c31", - "0x400280007ff97fff", - "0x400380017ff97ff8", - "0x400280027ff97ffc", - "0x400280037ff97ffd", - "0x400280047ff97ffe", - "0x480280067ff98000", - "0x20680017fff7fff", - "0x7", - "0x480280057ff98000", - "0x482680017ff98000", - "0x7", - "0x10780017fff7fff", - "0x5", - "0x480280057ff98000", - "0x482680017ff98000", - "0x9", "0x480680017fff8000", "0x56414c4944", "0x208b7fff7fff7ffe", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", + "0x40780017fff7fff", "0x1", - "0x20780017fff7ffd", - "0x7", + "0x480680017fff8000", + "0x53455155454e4345525f4d49534d41544348", + "0x400080007ffe7fff", + "0x48127ff07fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480680017fff8000", "0x1", - "0x48307ffe80007fff", - "0x10780017fff7fff", - "0x5", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x20680017fff7fff", - "0x9", "0x40780017fff7fff", - "0x8e", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0xc", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffd02", - "0x20680017fff7ffd", - "0xc", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", + "0x1", "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x424c4f434b5f54494d455354414d505f4d49534d41544348", + "0x400080007ffe7fff", + "0x48127ff07fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480680017fff8000", "0x1", "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x4e", - "0x20680017fff7ffd", - "0xa", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x40780017fff7fff", + "0x2", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", + "0x424c4f434b5f4e554d4245525f4d49534d41544348", + "0x400080007ffe7fff", + "0x48127ff07fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480680017fff8000", - "0x0", - "0x480080017ffb8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x40780017fff7fff", + "0xc", + "0x48127ff07fff8000", + "0x480080027fcd8000", + "0x482480017fcc8000", + "0x6", "0x480680017fff8000", "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x480080047fca8000", + "0x480080057fc98000", "0x208b7fff7fff7ffe", - "0x48297ffb80007ffc", - "0xa0680017fff8000", - "0x6", - "0x48317ffe80007ffd", - "0x400280007ffa7fff", - "0x10780017fff7fff", + "0x40780017fff7fff", "0x10", - "0x482680017ffd8000", - "0x1", - "0x48307fff80007ffd", - "0x400280007ffa7fff", "0x40780017fff7fff", "0x1", - "0x482680017ffa8000", - "0x1", "0x480680017fff8000", - "0x0", + "0x496e646578206f7574206f6620626f756e6473", + "0x400080007ffe7fff", + "0x482480017fd78000", + "0x3", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", - "0x0", - "0x482a7ffd7ffb8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", "0x40780017fff7fff", + "0x17", + "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x496e646578206f7574206f6620626f756e6473", "0x400080007ffe7fff", - "0x482680017ffa8000", - "0x1", + "0x482480017fd78000", + "0x2", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x1", - "0x48127ffc7fff8000", - "0x482480017ffb8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x7", + "0x40780017fff7fff", + "0x1e", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe", + "0x496e646578206f7574206f6620626f756e6473", + "0x400080007ffe7fff", + "0x482480017fd78000", + "0x1", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x8", - "0x480680017fff8000", - "0x0", + "0x40780017fff7fff", + "0x22", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe", + "0x556e6b6e6f776e207363656e6172696f", + "0x400080007ffe7fff", + "0x48127fd77fff8000", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x2a", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x476574457865637574696f6e496e666f", - "0x400280007ffd7fff", - "0x400380017ffd7ffc", - "0x480280037ffd8000", - "0x20680017fff7fff", - "0xc", - "0x480280027ffd8000", - "0x482680017ffd8000", - "0x5", - "0x480680017fff8000", - "0x0", + "0x496e646578206f7574206f6620626f756e6473", + "0x400080007ffe7fff", + "0x482680017ffb8000", + "0x1", + "0x48127fcc7fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", - "0x0", - "0x480280047ffd8000", - "0x10780017fff7fff", - "0x9", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x38", + "0x480a7ffb7fff8000", "0x480280027ffd8000", "0x482680017ffd8000", "0x6", @@ -1827,41 +1733,16 @@ "0x1", "0x480280047ffd8000", "0x480280057ffd8000", - "0x1104800180018000", - "0x7f", - "0x20680017fff7ffd", - "0xa", - "0x48127ff67fff8000", - "0x48127ff67fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff67fff8000", - "0x48127ff67fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x79", - "0x482480017fff8000", - "0x78", - "0x480080007fff8000", "0xa0680017fff8000", - "0x9", - "0x4825800180007ff8", - "0x12a2", - "0x482480017fff8000", - "0x100000000000000000000000000000000", + "0x7", + "0x482680017ff88000", + "0xfffffffffffffffffffffffffffff6be", "0x400280007ff77fff", "0x10780017fff7fff", - "0x4c", + "0x43", "0x4825800180007ff8", - "0x12a2", + "0x942", "0x400280007ff77fff", "0x482680017ff78000", "0x1", @@ -1878,15 +1759,30 @@ "0x480a7ffb7fff8000", "0x480a7ffc7fff8000", "0x208b7fff7fff7ffe", + "0x48297ff980007ffa", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ff98000", + "0x1", + "0x480a7ffa7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ff98000", + "0x10780017fff7fff", + "0x8", "0x480a7ff97fff8000", "0x480a7ffa7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffc2b", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffe", - "0x27", - "0x400280007ffc7fff", - "0x48127ff07fff8000", - "0x48127fee7fff8000", + "0xf", + "0x400280007ffc7fff", + "0x48127ffa7fff8000", + "0x48127ff87fff8000", "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x480a7ffb7fff8000", @@ -1895,34 +1791,10 @@ "0x4825800180007ffd", "0x1", "0x1104800180018000", - "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffd1", - "0x20680017fff7ffa", - "0xc", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", + "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffc9", "0x208b7fff7fff7ffe", + "0x48127ffa7fff8000", "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff07fff8000", - "0x48127fee7fff8000", "0x480680017fff8000", "0x0", "0x48127ff97fff8000", @@ -1953,21 +1825,18 @@ "0x48127ff87fff8000", "0x482480017ff78000", "0x1", - "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x8", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", "0x208b7fff7fff7ffe" ], + "bytecode_segment_lengths": [ + 141, + 249, + 311, + 294, + 89, + 174, + 474, + 92 + ], "hints": [ [ 0, @@ -1992,7 +1861,7 @@ ] ], [ - 25, + 38, [ { "AllocSegment": { @@ -2005,17 +1874,17 @@ ] ], [ - 44, + 57, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x8354" + "Immediate": "0x5730" }, "rhs": { "Deref": { "register": "AP", - "offset": -23 + "offset": -12 } }, "dst": { @@ -2027,7 +1896,7 @@ ] ], [ - 64, + 77, [ { "AllocSegment": { @@ -2040,7 +1909,7 @@ ] ], [ - 87, + 97, [ { "AllocSegment": { @@ -2053,7 +1922,7 @@ ] ], [ - 102, + 112, [ { "AllocSegment": { @@ -2066,7 +1935,7 @@ ] ], [ - 116, + 126, [ { "AllocSegment": { @@ -2079,7 +1948,7 @@ ] ], [ - 131, + 141, [ { "TestLessThanOrEqual": { @@ -2101,7 +1970,7 @@ ] ], [ - 168, + 230, [ { "AllocSegment": { @@ -2114,17 +1983,17 @@ ] ], [ - 187, + 249, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x9cb8" + "Immediate": "0x6414" }, "rhs": { "Deref": { "register": "AP", - "offset": -57 + "offset": -24 } }, "dst": { @@ -2136,7 +2005,7 @@ ] ], [ - 210, + 298, [ { "AllocSegment": { @@ -2149,7 +2018,7 @@ ] ], [ - 233, + 318, [ { "AllocSegment": { @@ -2162,7 +2031,7 @@ ] ], [ - 248, + 333, [ { "AllocSegment": { @@ -2175,7 +2044,7 @@ ] ], [ - 262, + 347, [ { "AllocSegment": { @@ -2188,7 +2057,7 @@ ] ], [ - 276, + 361, [ { "AllocSegment": { @@ -2201,7 +2070,7 @@ ] ], [ - 290, + 375, [ { "AllocSegment": { @@ -2214,12 +2083,12 @@ ] ], [ - 307, + 390, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x1270" + "Immediate": "0x0" }, "rhs": { "Deref": { @@ -2236,7 +2105,100 @@ ] ], [ - 350, + 423, + [ + { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "rhs": { + "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + }, + "dst": { + "register": "AP", + "offset": 4 + } + } + } + ] + ], + [ + 427, + [ + { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": 3 + } + }, + "scalar": { + "Immediate": "0x110000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -2 + }, + "y": { + "register": "AP", + "offset": -1 + } + } + } + ] + ], + [ + 437, + [ + { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": -2 + } + }, + "scalar": { + "Immediate": "0x8000000000000000000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -1 + }, + "y": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 489, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 536, [ { "AllocSegment": { @@ -2249,17 +2211,17 @@ ] ], [ - 369, + 555, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0xcb0c" + "Immediate": "0x9bdc" }, "rhs": { "Deref": { "register": "AP", - "offset": -13 + "offset": -11 } }, "dst": { @@ -2271,7 +2233,48 @@ ] ], [ - 393, + 578, + [ + { + "SystemCall": { + "system": { + "Deref": { + "register": "FP", + "offset": -5 + } + } + } + } + ] + ], + [ + 589, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 622, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 637, [ { "AllocSegment": { @@ -2284,7 +2287,7 @@ ] ], [ - 416, + 651, [ { "AllocSegment": { @@ -2297,7 +2300,7 @@ ] ], [ - 431, + 672, [ { "AllocSegment": { @@ -2310,7 +2313,7 @@ ] ], [ - 453, + 686, [ { "AllocSegment": { @@ -2323,7 +2326,109 @@ ] ], [ - 467, + 701, + [ + { + "TestLessThanOrEqual": { + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "register": "FP", + "offset": -6 + } + }, + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 734, + [ + { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "rhs": { + "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + }, + "dst": { + "register": "AP", + "offset": 4 + } + } + } + ] + ], + [ + 738, + [ + { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": 3 + } + }, + "scalar": { + "Immediate": "0x110000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -2 + }, + "y": { + "register": "AP", + "offset": -1 + } + } + } + ] + ], + [ + 748, + [ + { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": -2 + } + }, + "scalar": { + "Immediate": "0x8000000000000000000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -1 + }, + "y": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 800, [ { "AllocSegment": { @@ -2336,7 +2441,7 @@ ] ], [ - 481, + 847, [ { "AllocSegment": { @@ -2349,17 +2454,17 @@ ] ], [ - 498, + 866, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x1270" + "Immediate": "0x28b4" }, "rhs": { "Deref": { - "register": "FP", - "offset": -6 + "register": "AP", + "offset": -11 } }, "dst": { @@ -2371,42 +2476,22 @@ ] ], [ - 541, - [ - { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 - } - } - } - ] - ], - [ - 560, + 889, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x319c" - }, - "rhs": { + "SystemCall": { + "system": { "Deref": { - "register": "AP", - "offset": -13 + "register": "FP", + "offset": -5 } - }, - "dst": { - "register": "AP", - "offset": 0 } } } ] ], [ - 580, + 892, [ { "AllocSegment": { @@ -2419,7 +2504,7 @@ ] ], [ - 596, + 916, [ { "AllocSegment": { @@ -2432,7 +2517,7 @@ ] ], [ - 611, + 931, [ { "AllocSegment": { @@ -2445,7 +2530,7 @@ ] ], [ - 633, + 945, [ { "AllocSegment": { @@ -2458,7 +2543,7 @@ ] ], [ - 647, + 966, [ { "AllocSegment": { @@ -2471,7 +2556,7 @@ ] ], [ - 661, + 980, [ { "AllocSegment": { @@ -2484,7 +2569,7 @@ ] ], [ - 676, + 995, [ { "TestLessThanOrEqual": { @@ -2506,7 +2591,7 @@ ] ], [ - 695, + 1012, [ { "AllocSegment": { @@ -2519,7 +2604,7 @@ ] ], [ - 714, + 1031, [ { "TestLessThanOrEqual": { @@ -2529,7 +2614,7 @@ "rhs": { "Deref": { "register": "AP", - "offset": -8 + "offset": -7 } }, "dst": { @@ -2541,7 +2626,7 @@ ] ], [ - 728, + 1043, [ { "AllocSegment": { @@ -2554,7 +2639,7 @@ ] ], [ - 739, + 1054, [ { "AllocSegment": { @@ -2567,7 +2652,7 @@ ] ], [ - 754, + 1069, [ { "AllocSegment": { @@ -2580,7 +2665,7 @@ ] ], [ - 769, + 1084, [ { "TestLessThanOrEqual": { @@ -2602,7 +2687,7 @@ ] ], [ - 794, + 1131, [ { "AllocSegment": { @@ -2615,17 +2700,17 @@ ] ], [ - 813, + 1150, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x89f8" + "Immediate": "0x5d0c" }, "rhs": { "Deref": { "register": "AP", - "offset": -27 + "offset": -14 } }, "dst": { @@ -2637,7 +2722,7 @@ ] ], [ - 834, + 1196, [ { "AllocSegment": { @@ -2650,7 +2735,7 @@ ] ], [ - 852, + 1214, [ { "AllocSegment": { @@ -2663,7 +2748,7 @@ ] ], [ - 867, + 1229, [ { "AllocSegment": { @@ -2676,7 +2761,7 @@ ] ], [ - 881, + 1243, [ { "AllocSegment": { @@ -2689,7 +2774,47 @@ ] ], [ - 990, + 1262, + [ + { + "SystemCall": { + "system": { + "Deref": { + "register": "FP", + "offset": -3 + } + } + } + } + ] + ], + [ + 1277, + [ + { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -6 + } + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 1329, [ { "AllocSegment": { @@ -2702,7 +2827,32 @@ ] ], [ - 1019, + 1352, + [ + { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -2 + } + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 1363, [ { "AllocSegment": { @@ -2715,7 +2865,7 @@ ] ], [ - 1022, + 1366, [ { "TestLessThan": { @@ -2737,7 +2887,7 @@ ] ], [ - 1026, + 1370, [ { "LinearSplit": { @@ -2766,7 +2916,7 @@ ] ], [ - 1036, + 1380, [ { "LinearSplit": { @@ -2795,14 +2945,14 @@ ] ], [ - 1058, + 1400, [ { "SystemCall": { "system": { "Deref": { "register": "AP", - "offset": -94 + "offset": -49 } } } @@ -2810,7 +2960,7 @@ ] ], [ - 1101, + 1425, [ { "AllocSegment": { @@ -2823,22 +2973,7 @@ ] ], [ - 1139, - [ - { - "SystemCall": { - "system": { - "Deref": { - "register": "AP", - "offset": -94 - } - } - } - } - ] - ], - [ - 1246, + 1442, [ { "AllocSegment": { @@ -2851,23 +2986,37 @@ ] ], [ - 1262, + 1470, [ { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 + "SystemCall": { + "system": { + "Deref": { + "register": "AP", + "offset": -49 + } } } } ] ], [ - 1278, + 1500, [ { - "AllocSegment": { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -2 + } + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, "dst": { "register": "AP", "offset": 0 @@ -2877,10 +3026,22 @@ ] ], [ - 1334, + 1515, [ { - "AllocSegment": { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -3 + } + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, "dst": { "register": "AP", "offset": 0 @@ -2890,78 +3051,64 @@ ] ], [ - 1480, + 1530, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -3 } }, "rhs": { - "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + "Deref": { + "register": "AP", + "offset": -1 + } }, "dst": { "register": "AP", - "offset": 4 + "offset": 0 } } } ] ], [ - 1484, + 1548, [ { - "LinearSplit": { - "value": { + "SystemCall": { + "system": { "Deref": { "register": "AP", - "offset": 3 + "offset": -37 } - }, - "scalar": { - "Immediate": "0x110000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -2 - }, - "y": { - "register": "AP", - "offset": -1 } } } ] ], [ - 1494, + 1580, [ { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": -2 - } - }, - "scalar": { - "Immediate": "0x8000000000000000000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { + "AllocSegment": { + "dst": { "register": "AP", - "offset": -1 - }, - "y": { + "offset": 0 + } + } + } + ] + ], + [ + 1596, + [ + { + "AllocSegment": { + "dst": { "register": "AP", "offset": 0 } @@ -2970,7 +3117,7 @@ ] ], [ - 1554, + 1612, [ { "AllocSegment": { @@ -2983,52 +3130,36 @@ ] ], [ - 1614, + 1639, [ { - "SystemCall": { - "system": { - "Deref": { - "register": "FP", - "offset": -7 - } + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1660, + 1656, [ { - "SystemCall": { - "system": { - "Deref": { - "register": "FP", - "offset": -7 - } + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1743, + 1673, [ { - "TestLessThan": { - "lhs": { - "Deref": { - "register": "FP", - "offset": -3 - } - }, - "rhs": { - "Deref": { - "register": "AP", - "offset": -1 - } - }, + "AllocSegment": { "dst": { "register": "AP", "offset": 0 @@ -3038,7 +3169,7 @@ ] ], [ - 1763, + 1690, [ { "AllocSegment": { @@ -3051,27 +3182,25 @@ ] ], [ - 1805, + 1706, [ { - "SystemCall": { - "system": { - "Deref": { - "register": "FP", - "offset": -3 - } + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1849, + 1732, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x12a2" + "Immediate": "0x942" }, "rhs": { "Deref": { @@ -3088,7 +3217,7 @@ ] ], [ - 1932, + 1804, [ { "AllocSegment": { @@ -3105,21 +3234,21 @@ "EXTERNAL": [ { "selector": "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad", - "offset": 496, + "offset": 701, "builtins": [ "range_check" ] }, { "selector": "0x162da33a4585851fe8d3af3c2a9c60b557814e221e0d4f30ff0b2189d9c7775", - "offset": 305, + "offset": 390, "builtins": [ "range_check" ] }, { "selector": "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", - "offset": 676, + "offset": 995, "builtins": [ "range_check" ] @@ -3133,7 +3262,7 @@ }, { "selector": "0x36fcbf06cd96843058359e1a75928beacfac10727dab22a3972f0af8aa92895", - "offset": 131, + "offset": 141, "builtins": [ "range_check" ] @@ -3143,11 +3272,11 @@ "CONSTRUCTOR": [ { "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "offset": 769, + "offset": 1084, "builtins": [ "range_check" ] } ] } -} \ No newline at end of file +} diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/account_with_dummy_validate.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/account_with_dummy_validate.casm.json index 82859bf765d..1037d4c7b4c 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/account_with_dummy_validate.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/account_with_dummy_validate.casm.json @@ -2330,4 +2330,4 @@ "L1_HANDLER": [], "CONSTRUCTOR": [] } -} \ No newline at end of file +} diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/account_with_long_validate.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/account_with_long_validate.casm.json index c3941c37f35..48807a6494f 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/account_with_long_validate.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/account_with_long_validate.casm.json @@ -1,46 +1,104 @@ { "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "compiler_version": "2.4.0", + "compiler_version": "2.6.0", "bytecode": [ "0xa0680017fff8000", "0x7", "0x482680017ffa8000", - "0xfffffffffffffffffffffffffffffeac", + "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0xae", + "0xee", "0x4825800180007ffa", - "0x154", + "0x0", "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x343", - "0x482680017ff98000", + "0x480680017fff8000", "0x1", - "0x20680017fff7ffd", - "0x95", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xc6", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", + "0x1", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x480080007ff88000", + "0x10780017fff7fff", + "0x8", "0x48127ffb7fff8000", "0x48127ffb7fff8000", - "0x1104800180018000", - "0x33b", - "0x20680017fff7ffe", - "0x81", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x335", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffe", - "0x6d", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x32f", + "0xa3", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", + "0x1", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x480080007ff88000", + "0x10780017fff7fff", + "0x8", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffe", - "0x59", + "0x80", "0x48307ffc80007ffd", - "0x4824800180007fff", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", + "0x1", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x480080007ff88000", + "0x10780017fff7fff", + "0x8", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0x5d", + "0x48307ffc80007ffd", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -50,8 +108,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127fce7fff8000", - "0x48127fbd7fff8000", + "0x48127fe87fff8000", + "0x48127fe67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -60,47 +118,53 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x5ee", + "0x4cc", "0x482480017fff8000", - "0x5ed", + "0x4cb", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fbb", - "0x14fa", + "0x4824800180007fe4", + "0x0", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007fc97fff", + "0x400080007fe37fff", "0x10780017fff7fff", - "0x27", - "0x4824800180007fbb", - "0x14fa", - "0x400080007fca7fff", - "0x482480017fca8000", + "0x2d", + "0x4824800180007fe4", + "0x0", + "0x400080007fe47fff", + "0x482480017fe48000", "0x1", - "0x48127ffe7fff8000", - "0x48127fc77fff8000", - "0x48127fd67fff8000", - "0x48127fe47fff8000", - "0x48127ff27fff8000", + "0x20680017fff7ff2", + "0x6", + "0x48127fff7fff8000", + "0x48127ffd7fff8000", + "0x10780017fff7fff", + "0xc", + "0x48127fff7fff8000", + "0x48127ffd7fff8000", + "0x480680017fff8000", + "0x989680", "0x1104800180018000", - "0x322", + "0x427", "0x20680017fff7ffd", - "0x11", + "0x12", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x337", - "0x48127ff37fff8000", - "0x48127ff37fff8000", + "0x480680017fff8000", + "0x56414c4944", + "0x400080007ffe7fff", + "0x48127ffc7fff8000", + "0x48127ffc7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x0", "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", "0x48127ffb7fff8000", "0x48127ffb7fff8000", @@ -115,9 +179,9 @@ "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017fc78000", + "0x482480017fe18000", "0x1", - "0x48127fb67fff8000", + "0x48127fdf7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -130,8 +194,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202334", "0x400080007ffe7fff", - "0x48127fd07fff8000", - "0x48127fbf7fff8000", + "0x48127fe97fff8000", + "0x48127fe77fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -144,8 +208,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202333", "0x400080007ffe7fff", - "0x48127fdf7fff8000", - "0x48127fce7fff8000", + "0x48127fee7fff8000", + "0x48127fec7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -158,8 +222,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202332", "0x400080007ffe7fff", - "0x48127fee7fff8000", - "0x48127fdd7fff8000", + "0x48127ff37fff8000", + "0x48127ff17fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -172,8 +236,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ffd7fff8000", - "0x48127fec7fff8000", + "0x48127ff87fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -202,21 +266,34 @@ "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0x6f", + "0x7c", "0x4825800180007ffa", "0x0", "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x281", - "0x482680017ff98000", + "0x480680017fff8000", "0x1", - "0x20680017fff7ffd", - "0x56", - "0x48307ffb80007ffc", - "0x4824800180007fff", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0x54", + "0x48307ffc80007ffd", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -226,8 +303,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127ffb7fff8000", - "0x48127fea7fff8000", + "0x48127ff77fff8000", + "0x48127ff57fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -236,44 +313,44 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x53e", + "0x409", "0x482480017fff8000", - "0x53d", + "0x408", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fe8", + "0x4824800180007ff3", "0x0", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007ff67fff", + "0x400080007ff27fff", "0x10780017fff7fff", "0x24", - "0x4824800180007fe8", + "0x4824800180007ff3", "0x0", - "0x400080007ff77fff", - "0x482480017ff78000", + "0x400080007ff37fff", + "0x482480017ff38000", "0x1", "0x48127ffe7fff8000", - "0x48127ff47fff8000", + "0x480680017fff8000", + "0x989680", "0x1104800180018000", - "0x298", + "0x36b", "0x20680017fff7ffd", - "0x11", + "0x10", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x28a", - "0x48127ff37fff8000", - "0x48127ff37fff8000", + "0x480680017fff8000", + "0x56414c4944", + "0x400080007ffe7fff", + "0x48127ff97fff8000", + "0x48127ff97fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x0", "0x48127ffa7fff8000", - "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", "0x48127ffb7fff8000", "0x48127ffb7fff8000", @@ -288,9 +365,9 @@ "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017ff48000", + "0x482480017ff08000", "0x1", - "0x48127fe37fff8000", + "0x48127fee7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -303,8 +380,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ffd7fff8000", - "0x48127fec7fff8000", + "0x48127ff87fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -327,145 +404,127 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x2", "0xa0680017fff8000", "0x7", "0x482680017ffa8000", - "0xffffffffffffffffffffffffffffed90", + "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0xa8", + "0x10b", "0x4825800180007ffa", - "0x1270", + "0x0", "0x400280007ff97fff", "0x482680017ff98000", "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x257", - "0x20680017fff7ffe", - "0x8f", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x1f4", - "0x40137ff07fff8000", - "0x20680017fff7ffe", - "0x7a", - "0x48127fec7fff8000", - "0x48127fd07fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x40137ffb7fff8001", - "0x1104800180018000", - "0x280", - "0x20680017fff7ffa", - "0x69", - "0x20680017fff7ffd", - "0x59", - "0x48307ffb80007ffc", - "0x4824800180007fff", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0xe0", + "0xa0680017fff8004", + "0xe", + "0x4824800180047ffe", + "0x800000000000000000000000000000000000000000000000000000000000000", + "0x484480017ffe8000", + "0x110000000000000000", + "0x48307ffe7fff8002", + "0x480080007ff67ffc", + "0x480080017ff57ffc", + "0x402480017ffb7ffd", + "0xffffffffffffffeeffffffffffffffff", + "0x400080027ff47ffd", + "0x10780017fff7fff", + "0xce", + "0x484480017fff8001", + "0x8000000000000000000000000000000", + "0x48307fff80007ffd", + "0x480080007ff77ffd", + "0x480080017ff67ffd", + "0x402480017ffc7ffe", + "0xf8000000000000000000000000000000", + "0x400080027ff57ffe", + "0x482480017ff58000", + "0x3", + "0x48307ff680007ff7", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0x10", - "0x40780017fff7fff", + "0xa", + "0x482480017ff58000", "0x1", + "0x48127ff57fff8000", "0x480680017fff8000", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x400080007ffe7fff", - "0x48127ff47fff8000", - "0x48127ff47fff8000", - "0x480a7ffb7fff8000", + "0x0", + "0x480080007ff28000", + "0x10780017fff7fff", + "0x8", + "0x48127ff57fff8000", + "0x48127ff57fff8000", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x4a7", - "0x482480017fff8000", - "0x4a6", - "0x480080007fff8000", - "0xa0680017fff8000", - "0x9", - "0x4824800180007ff2", - "0x1432", - "0x482480017fff8000", - "0x100000000000000000000000000000000", - "0x400080007fef7fff", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xa1", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", "0x10780017fff7fff", - "0x27", - "0x4824800180007ff2", - "0x1432", - "0x400080007ff07fff", - "0x482480017ff08000", + "0xa", + "0x482480017ffb8000", "0x1", - "0x48127ffe7fff8000", - "0x480a80007fff8000", - "0x480a80017fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", - "0x1104800180018000", - "0x295", - "0x20680017fff7ffd", - "0x11", - "0x40780017fff7fff", - "0x1", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x48127ffd7fff8000", - "0x1104800180018000", - "0x1f0", - "0x48127ff37fff8000", - "0x48127ff37fff8000", - "0x480a7ffb7fff8000", + "0x48127ffb7fff8000", "0x480680017fff8000", "0x0", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", + "0x48127ff87fff8000", + "0x10780017fff7fff", + "0x8", "0x48127ffb7fff8000", "0x48127ffb7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482480017fed8000", "0x1", - "0x48127fed7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", + "0x0", + "0x20680017fff7ffe", + "0x20", "0x40780017fff7fff", "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202333", - "0x400080007ffe7fff", - "0x48127ff67fff8000", - "0x48127ff67fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", + "0x48127ff47fff8000", + "0x48127fe77fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ffb7fff8000", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", + "0x480080007ff88000", + "0x1104800180018000", + "0x2e5", + "0x20680017fff7ffa", + "0xb", + "0x48127ff87fff8000", + "0x48127ff87fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x10780017fff7fff", + "0x14", "0x48127ff87fff8000", "0x48127ff87fff8000", "0x480a7ffb7fff8000", @@ -474,90 +533,19 @@ "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202332", - "0x400080007ffe7fff", - "0x48127fea7fff8000", - "0x48127fce7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", + "0x48127ff57fff8000", + "0x48127fe87fff8000", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4661696c656420746f20646573657269616c697a6520706172616d202331", - "0x400080007ffe7fff", - "0x48127ff97fff8000", - "0x48127fdd7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482680017ff98000", "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x2", - "0xa0680017fff8000", - "0x7", - "0x482680017ffa8000", - "0xffffffffffffffffffffffffffffed90", - "0x400280007ff97fff", - "0x10780017fff7fff", - "0xa2", - "0x4825800180007ffa", - "0x1270", - "0x400280007ff97fff", - "0x482680017ff98000", - "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x199", - "0x20680017fff7ffe", - "0x89", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x1104800180018000", - "0x136", - "0x40137ff07fff8000", - "0x20680017fff7ffe", - "0x74", - "0x48127fec7fff8000", - "0x48127fd07fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x40137ffb7fff8001", - "0x1104800180018000", - "0x1c2", - "0x20680017fff7ffa", - "0x63", + "0x0", + "0x480680017fff8000", + "0x0", "0x20680017fff7ffd", - "0x53", + "0x54", "0x48307ffb80007ffc", - "0x4824800180007fff", - "0x0", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", @@ -567,8 +555,8 @@ "0x480680017fff8000", "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", "0x400080007ffe7fff", - "0x48127ff47fff8000", - "0x48127ff47fff8000", + "0x48127ff67fff8000", + "0x48127ff67fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -577,58 +565,61 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x3e9", + "0x30d", "0x482480017fff8000", - "0x3e8", + "0x30c", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007ff2", - "0x6fb8", + "0x4824800180007ff4", + "0x50a", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007fef7fff", + "0x400080007ff17fff", "0x10780017fff7fff", - "0x21", - "0x4824800180007ff2", - "0x6fb8", - "0x400080007ff07fff", - "0x48127fff7fff8000", - "0x480a7ffb7fff8000", - "0x480a80007fff8000", - "0x480a80017fff8000", - "0x48127ff27fff8000", - "0x48127ff27fff8000", + "0x24", + "0x4824800180007ff4", + "0x50a", + "0x400080007ff27fff", + "0x482480017ff28000", + "0x1", + "0x48127ffe7fff8000", + "0x480680017fff8000", + "0x989680", "0x1104800180018000", - "0x1ee", - "0x482480017fac8000", + "0x26f", + "0x20680017fff7ffd", + "0x10", + "0x40780017fff7fff", "0x1", - "0x20680017fff7ffc", - "0xa", - "0x48127fff7fff8000", + "0x480680017fff8000", + "0x56414c4944", + "0x400080007ffe7fff", "0x48127ff97fff8000", "0x48127ff97fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x0", - "0x48127ff97fff8000", - "0x48127ff97fff8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x48127fff7fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017fed8000", + "0x482480017fef8000", "0x1", - "0x48127fed7fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -641,8 +632,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202333", "0x400080007ffe7fff", - "0x48127ff67fff8000", - "0x48127ff67fff8000", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -650,21 +641,13 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202332", "0x400080007ffe7fff", - "0x48127fea7fff8000", - "0x48127fce7fff8000", + "0x48127ff87fff8000", + "0x48127feb7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -672,13 +655,20 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x482480017ff48000", + "0x3", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", + "0x6", + "0x48127ff47fff8000", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", - "0x48127ff97fff8000", - "0x48127fdd7fff8000", + "0x48127ffd7fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -701,104 +691,283 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x2", "0xa0680017fff8000", "0x7", "0x482680017ffa8000", "0x100000000000000000000000000000000", "0x400280007ff97fff", "0x10780017fff7fff", - "0x80", + "0x138", "0x4825800180007ffa", "0x0", "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", + "0x48297ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482680017ffc8000", + "0x1", + "0x480a7ffd7fff8000", + "0x480680017fff8000", + "0x0", + "0x480280007ffc8000", + "0x10780017fff7fff", + "0x8", "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x88", - "0x482680017ff98000", + "0x480680017fff8000", "0x1", - "0x20680017fff7ffd", - "0x67", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x1104800180018000", - "0x80", - "0x20680017fff7ffe", - "0x53", - "0x48307ffc80007ffd", - "0x4824800180007fff", + "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0x10d", + "0x40137fff7fff8001", + "0xa0680017fff8004", + "0xe", + "0x4825800180048001", + "0x800000000000000000000000000000000000000000000000000000000000000", + "0x484480017ffe8000", + "0x110000000000000000", + "0x48307ffe7fff8002", + "0x480080007ff67ffc", + "0x480080017ff57ffc", + "0x402480017ffb7ffd", + "0xffffffffffffffeeffffffffffffffff", + "0x400080027ff47ffd", + "0x10780017fff7fff", + "0xfa", + "0x484480017fff8001", + "0x8000000000000000000000000000000", + "0x48317fff80008001", + "0x480080007ff77ffd", + "0x480080017ff67ffd", + "0x402480017ffc7ffe", + "0xf8000000000000000000000000000000", + "0x400080027ff57ffe", + "0x482480017ff58000", + "0x3", + "0x48307ff680007ff7", "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0x10", - "0x40780017fff7fff", + "0xa", + "0x482480017ff58000", "0x1", + "0x48127ff57fff8000", "0x480680017fff8000", - "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", - "0x400080007ffe7fff", - "0x48127fec7fff8000", - "0x48127fdb7fff8000", - "0x480a7ffb7fff8000", + "0x0", + "0x480080007ff28000", + "0x10780017fff7fff", + "0x8", + "0x48127ff57fff8000", + "0x48127ff57fff8000", "0x480680017fff8000", "0x1", - "0x48127ffa7fff8000", - "0x482480017ff98000", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0xcd", + "0x40137fff7fff8000", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", "0x1", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x33f", - "0x482480017fff8000", - "0x33e", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x48127ff87fff8000", + "0x10780017fff7fff", + "0x8", + "0x48127ffb7fff8000", + "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffe", + "0x20", + "0x40780017fff7fff", + "0x1", + "0x48127ff47fff8000", + "0x48127fe77fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ffb7fff8000", + "0x48127ffa7fff8000", + "0x480080007ff88000", + "0x1104800180018000", + "0x1c2", + "0x20680017fff7ffa", + "0xb", + "0x48127ff87fff8000", + "0x48127ff87fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x10780017fff7fff", + "0x14", + "0x48127ff87fff8000", + "0x48127ff87fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x208b7fff7fff7ffe", + "0x48127ff57fff8000", + "0x48127fe87fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", + "0x480680017fff8000", + "0x1", + "0x480680017fff8000", + "0x0", + "0x480680017fff8000", + "0x0", + "0x20680017fff7ffd", + "0x7f", + "0x48307ffb80007ffc", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0x10", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x400080007ffe7fff", + "0x48127ff67fff8000", + "0x48127ff67fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x1104800180018000", + "0x1ea", + "0x482480017fff8000", + "0x1e9", "0x480080007fff8000", "0xa0680017fff8000", "0x9", - "0x4824800180007fd9", - "0x2a58", + "0x4824800180007ff4", + "0x54d8", "0x482480017fff8000", "0x100000000000000000000000000000000", - "0x400080007fe77fff", + "0x400080007ff17fff", "0x10780017fff7fff", - "0x21", - "0x4824800180007fd9", - "0x2a58", - "0x400080007fe87fff", - "0x48127fff7fff8000", - "0x480a7ffb7fff8000", - "0x48127fe57fff8000", - "0x48127ff47fff8000", - "0x1104800180018000", - "0x197", - "0x482480017fc58000", + "0x4f", + "0x4824800180007ff4", + "0x54d8", + "0x400080007ff27fff", + "0x482480017ff28000", "0x1", - "0x20680017fff7ffc", - "0xc", + "0x480680017fff8000", + "0x476574457865637574696f6e496e666f", + "0x400280007ffb7fff", + "0x400280017ffb7ffd", + "0x480280037ffb8000", + "0x20680017fff7fff", + "0x3a", + "0x480280047ffb8000", + "0x480080027fff8000", + "0x480280027ffb8000", + "0x482680017ffb8000", + "0x5", + "0x20680017fff7ffd", + "0x25", + "0x480680017fff8000", + "0x43616c6c436f6e7472616374", + "0x400080007ffe7fff", + "0x400080017ffe7ffd", + "0x400180027ffe8001", + "0x400180037ffe8000", + "0x400080047ffe7fef", + "0x400080057ffe7ff0", + "0x480080077ffe8000", + "0x20680017fff7fff", + "0xb", + "0x48127ff77fff8000", + "0x480080067ffc8000", + "0x482480017ffb8000", + "0xa", + "0x480680017fff8000", + "0x0", + "0x480080087ff98000", + "0x480080097ff88000", + "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", - "0x48127ffe7fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", "0x480680017fff8000", - "0x0", + "0x526573756c743a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x48127ff57fff8000", + "0x480080067ffa8000", + "0x482480017ff98000", + "0xa", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x494e56414c49445f43414c4c4552", + "0x400080007ffe7fff", + "0x48127ff77fff8000", + "0x48127ffb7fff8000", "0x48127ffb7fff8000", + "0x480680017fff8000", + "0x1", "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x48127fff7fff8000", - "0x48127ff97fff8000", - "0x48127ff97fff8000", + "0x48127ffd7fff8000", + "0x480280027ffb8000", + "0x482680017ffb8000", + "0x6", "0x480680017fff8000", "0x1", - "0x48127ff97fff8000", - "0x48127ff97fff8000", + "0x480280047ffb8000", + "0x480280057ffb8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4f7574206f6620676173", "0x400080007ffe7fff", - "0x482480017fe58000", + "0x482480017fef8000", + "0x1", + "0x48127fef7fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", "0x1", - "0x48127fd47fff8000", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4661696c656420746f20646573657269616c697a6520706172616d202333", + "0x400080007ffe7fff", + "0x48127ff77fff8000", + "0x48127ff77fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -811,8 +980,8 @@ "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202332", "0x400080007ffe7fff", - "0x48127fee7fff8000", - "0x48127fdd7fff8000", + "0x48127ff87fff8000", + "0x48127feb7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -820,13 +989,20 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x482480017ff48000", + "0x3", + "0x10780017fff7fff", + "0x5", + "0x40780017fff7fff", + "0x6", + "0x48127ff47fff8000", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4661696c656420746f20646573657269616c697a6520706172616d202331", "0x400080007ffe7fff", "0x48127ffd7fff8000", - "0x48127fec7fff8000", + "0x48127fef7fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -849,6 +1025,18 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0xa0680017fff8000", + "0x7", + "0x482680017ffa8000", + "0x100000000000000000000000000000000", + "0x400280007ff97fff", + "0x10780017fff7fff", + "0xa4", + "0x4825800180007ffa", + "0x0", + "0x400280007ff97fff", + "0x482680017ff98000", + "0x1", "0x48297ffc80007ffd", "0x20680017fff7fff", "0x4", @@ -859,7 +1047,7 @@ "0x480a7ffd7fff8000", "0x480680017fff8000", "0x0", - "0x480a7ffc7fff8000", + "0x480280007ffc8000", "0x10780017fff7fff", "0x8", "0x480a7ffc7fff8000", @@ -868,575 +1056,168 @@ "0x1", "0x480680017fff8000", "0x0", - "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x8", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x480680017fff8000", - "0x0", - "0x480080007ffa8000", - "0x208b7fff7fff7ffe", - "0x48127ffe7fff8000", - "0x48127ffe7fff8000", - "0x480680017fff8000", + "0x20680017fff7ffe", + "0x7c", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0xa", + "0x482480017ffb8000", "0x1", + "0x48127ffb7fff8000", "0x480680017fff8000", "0x0", - "0x208b7fff7fff7ffe", - "0x4825800180007ffc", - "0x0", - "0x20680017fff7fff", - "0x6", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", + "0x480080007ff88000", "0x10780017fff7fff", - "0xa", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x1104800180018000", - "0x12e", - "0x20680017fff7ffd", - "0xb", + "0x8", "0x48127ffb7fff8000", "0x48127ffb7fff8000", "0x480680017fff8000", - "0x0", + "0x1", "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0x59", + "0x48307ffc80007ffd", + "0x20680017fff7fff", + "0x4", + "0x10780017fff7fff", + "0x10", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x400080007ffe7fff", + "0x48127ff27fff8000", + "0x48127ff07fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x400380007ffd7ffb", - "0x480a7ffc7fff8000", - "0x482680017ffd8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", - "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", "0x1104800180018000", - "0x113", - "0x20680017fff7ffd", - "0xb", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", + "0xf7", + "0x482480017fff8000", + "0xf6", + "0x480080007fff8000", + "0xa0680017fff8000", + "0x9", + "0x4824800180007fee", + "0x134c", + "0x482480017fff8000", + "0x100000000000000000000000000000000", + "0x400080007fed7fff", + "0x10780017fff7fff", + "0x29", + "0x4824800180007fee", + "0x134c", + "0x400080007fee7fff", "0x480680017fff8000", "0x0", "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x38077b29e57bba3aa860b08652a77bd1076d0f70e38f019ba9920b820cf78f6", + "0x482480017fec8000", + "0x1", "0x480680017fff8000", + "0x53746f726167655772697465", + "0x400280007ffb7fff", + "0x400280017ffb7ffb", + "0x400280027ffb7ffc", + "0x400280037ffb7ffd", + "0x400280047ffb7ff4", + "0x480280067ffb8000", + "0x20680017fff7fff", + "0xd", + "0x40780017fff7fff", "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffa2", - "0x20680017fff7ffe", - "0x2b", - "0xa0680017fff8004", - "0xe", - "0x4824800180047ffe", - "0x800000000000000000000000000000000000000000000000000000000000000", - "0x484480017ffe8000", - "0x110000000000000000", - "0x48307ffe7fff8002", - "0x480280007ffb7ffc", - "0x480280017ffb7ffc", - "0x402480017ffb7ffd", - "0xffffffffffffffeeffffffffffffffff", - "0x400280027ffb7ffd", - "0x10780017fff7fff", - "0x14", - "0x484480017fff8001", - "0x8000000000000000000000000000000", - "0x48307fff80007ffd", - "0x480280007ffb7ffd", - "0x480280017ffb7ffd", - "0x402480017ffc7ffe", - "0xf8000000000000000000000000000000", - "0x400280027ffb7ffe", - "0x40780017fff7fff", - "0x1", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff57fff8000", - "0x208b7fff7fff7ffe", - "0x482680017ffb8000", - "0x3", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x6", - "0x480a7ffb7fff8000", - "0x48127ff57fff8000", - "0x48127ff57fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48297ffc80007ffd", - "0x20680017fff7fff", - "0x4", - "0x10780017fff7fff", - "0xa", - "0x482680017ffc8000", - "0x1", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ffc7fff8000", - "0x10780017fff7fff", - "0x8", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", "0x48127ffc7fff8000", - "0x48127ffc7fff8000", - "0x20680017fff7ffc", - "0x26", - "0x40780017fff7fff", - "0x1", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x48127ffa7fff8000", - "0x480080007ff68000", - "0x1104800180018000", - "0xbe", - "0x20680017fff7ffa", - "0xc", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x1104800180018000", - "0x7c", - "0x20680017fff7ffd", - "0xb", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x56414c4944", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ff87fff8000", - "0x480a7ff97fff8000", - "0x1104800180018000", - "0xea", - "0x20680017fff7ffd", - "0x44", - "0x4824800180007fff", - "0x0", - "0x20680017fff7fff", - "0x31", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x43616c6c436f6e7472616374", - "0x400080007ff87fff", - "0x400080017ff87ff7", - "0x400180027ff87ffa", - "0x400180037ff87ffb", - "0x400080047ff87ffd", - "0x400080057ff87ffe", - "0x480080077ff88000", - "0x20680017fff7fff", - "0xb", - "0x480080067ff78000", - "0x482480017ff68000", - "0xa", - "0x480680017fff8000", - "0x0", - "0x480080087ff48000", - "0x480080097ff38000", - "0x10780017fff7fff", - "0x9", - "0x480080067ff78000", - "0x482480017ff68000", - "0xa", - "0x480680017fff8000", - "0x1", - "0x480080087ff48000", - "0x480080097ff38000", - "0x1104800180018000", - "0xda", - "0x20680017fff7ffd", - "0x9", - "0x48127fec7fff8000", - "0x48127fec7fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x48127fec7fff8000", - "0x48127fec7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x16", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x494e56414c49445f43414c4c4552", - "0x400080007ffe7fff", - "0x48127fe27fff8000", - "0x48127fe27fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x482480017ffa8000", - "0x1", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x19", - "0x48127fe27fff8000", - "0x48127fe27fff8000", - "0x480680017fff8000", - "0x1", - "0x48127fe27fff8000", - "0x48127fe27fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffa7fff8000", - "0x480a7ffb7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0xc0", - "0x20680017fff7ffd", - "0xb", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x989680", - "0x1104800180018000", - "0xdc", - "0x20680017fff7ffd", - "0xb", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x164", - "0x482480017fff8000", - "0x163", - "0x480080007fff8000", - "0xa0680017fff8000", - "0x9", - "0x4825800180007ff8", - "0x12a2", - "0x482480017fff8000", - "0x100000000000000000000000000000000", - "0x400280007ff77fff", - "0x10780017fff7fff", - "0x4c", - "0x4825800180007ff8", - "0x12a2", - "0x400280007ff77fff", - "0x482680017ff78000", - "0x1", - "0x20780017fff7ffd", - "0xd", - "0x48127fff7fff8000", - "0x48127ffd7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ff97fff8000", - "0x480a7ffa7fff8000", - "0x480680017fff8000", - "0x0", - "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ff97fff8000", - "0x480a7ffa7fff8000", - "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffffe6b", - "0x20680017fff7ffe", - "0x27", - "0x400280007ffc7fff", - "0x48127ff07fff8000", - "0x48127fee7fff8000", - "0x48127ffa7fff8000", - "0x48127ffa7fff8000", - "0x480a7ffb7fff8000", - "0x482680017ffc8000", - "0x1", - "0x4825800180007ffd", - "0x1", - "0x1104800180018000", - "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffd1", - "0x20680017fff7ffa", - "0xc", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x48127ff87fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff07fff8000", - "0x48127fee7fff8000", - "0x480680017fff8000", - "0x0", - "0x48127ff97fff8000", - "0x48127ff97fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x480680017fff8000", - "0x4f7574206f6620676173", - "0x400080007ffe7fff", - "0x482680017ff78000", - "0x1", - "0x480a7ff87fff8000", - "0x480680017fff8000", - "0x1", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x48127ff87fff8000", - "0x482480017ff78000", - "0x1", - "0x208b7fff7fff7ffe", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x1104800180018000", - "0x9e", - "0x20680017fff7ffd", - "0xa", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480080027ffb8000", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x480680017fff8000", - "0x526573756c743a3a756e77726170206661696c65642e", - "0x1104800180018000", - "0xb1", - "0x20680017fff7ffd", - "0x7", - "0x480680017fff8000", - "0x0", - "0x48127ffd7fff8000", - "0x48127ffd7fff8000", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x1", - "0x48127ffd7fff8000", - "0x48127ffd7fff8000", - "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x38077b29e57bba3aa860b08652a77bd1076d0f70e38f019ba9920b820cf78f6", - "0x480680017fff8000", - "0x53746f726167655772697465", - "0x400280007ffc7fff", - "0x400380017ffc7ffb", - "0x400280027ffc7ffd", - "0x400280037ffc7ffe", - "0x400380047ffc7ffd", - "0x480280067ffc8000", - "0x20680017fff7fff", - "0xd", - "0x480280057ffc8000", - "0x482680017ffc8000", + "0x480280057ffb8000", + "0x482680017ffb8000", "0x7", "0x480680017fff8000", "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x10780017fff7fff", - "0x9", - "0x480280057ffc8000", - "0x482680017ffc8000", + "0x48127ffb7fff8000", + "0x48127ffa7fff8000", + "0x208b7fff7fff7ffe", + "0x48127ffd7fff8000", + "0x480280057ffb8000", + "0x482680017ffb8000", "0x9", "0x480680017fff8000", "0x1", - "0x480280077ffc8000", - "0x480280087ffc8000", - "0x1104800180018000", - "0x95", - "0x20680017fff7ffd", - "0xb", - "0x48127ff67fff8000", - "0x48127ff67fff8000", + "0x480280077ffb8000", + "0x480280087ffb8000", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", + "0x4f7574206f6620676173", + "0x400080007ffe7fff", + "0x482480017feb8000", + "0x1", + "0x48127fe97fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", - "0x0", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", "0x480680017fff8000", - "0x0", + "0x4661696c656420746f20646573657269616c697a6520706172616d202332", + "0x400080007ffe7fff", + "0x48127ff37fff8000", + "0x48127ff17fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", + "0x400080007ffe7fff", + "0x48127ff87fff8000", "0x48127ff67fff8000", - "0x48127ff67fff8000", + "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7574206f6620676173", + "0x400080007ffe7fff", + "0x482680017ff98000", + "0x1", + "0x480a7ffa7fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", "0x208b7fff7fff7ffe", - "0x1104800180018000", - "0x9c", - "0x482480017fff8000", - "0x9b", - "0x480080007fff8000", "0xa0680017fff8000", - "0x9", - "0x4825800180007ffc", - "0x816", - "0x482480017fff8000", - "0x100000000000000000000000000000000", + "0x7", + "0x482680017ffc8000", + "0xfffffffffffffffffffffffffffffbd2", "0x400280007ffb7fff", "0x10780017fff7fff", - "0x2a", + "0x19", "0x4825800180007ffc", - "0x816", + "0x42e", "0x400280007ffb7fff", "0x482680017ffb8000", "0x1", @@ -1456,24 +1237,7 @@ "0x4825800180007ffd", "0x1", "0x1104800180018000", - "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffdf", - "0x20680017fff7ffd", - "0xb", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x480680017fff8000", - "0x0", - "0x208b7fff7fff7ffe", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x480680017fff8000", - "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", + "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffe6", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", @@ -1489,95 +1253,108 @@ "0x482480017ffa8000", "0x1", "0x208b7fff7fff7ffe", - "0x480680017fff8000", - "0x476574457865637574696f6e496e666f", - "0x400280007ffd7fff", - "0x400380017ffd7ffc", - "0x480280037ffd8000", - "0x20680017fff7fff", - "0xc", - "0x480280027ffd8000", - "0x482680017ffd8000", - "0x5", + "0xa0680017fff8000", + "0x7", + "0x482680017ff88000", + "0xfffffffffffffffffffffffffffff6be", + "0x400280007ff77fff", + "0x10780017fff7fff", + "0x43", + "0x4825800180007ff8", + "0x942", + "0x400280007ff77fff", + "0x482680017ff78000", + "0x1", + "0x20780017fff7ffd", + "0xd", + "0x48127fff7fff8000", + "0x48127ffd7fff8000", "0x480680017fff8000", "0x0", + "0x480a7ff97fff8000", + "0x480a7ffa7fff8000", "0x480680017fff8000", "0x0", - "0x480280047ffd8000", + "0x480a7ffb7fff8000", + "0x480a7ffc7fff8000", + "0x208b7fff7fff7ffe", + "0x48297ff980007ffa", + "0x20680017fff7fff", + "0x4", "0x10780017fff7fff", - "0x9", - "0x480280027ffd8000", - "0x482680017ffd8000", - "0x6", - "0x480680017fff8000", - "0x1", - "0x480280047ffd8000", - "0x480280057ffd8000", - "0x1104800180018000", - "0x33", - "0x20680017fff7ffd", "0xa", - "0x48127ff67fff8000", - "0x48127ff67fff8000", - "0x480680017fff8000", - "0x0", + "0x482680017ff98000", + "0x1", + "0x480a7ffa7fff8000", "0x480680017fff8000", "0x0", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x48127ff67fff8000", - "0x48127ff67fff8000", + "0x480280007ff98000", + "0x10780017fff7fff", + "0x8", + "0x480a7ff97fff8000", + "0x480a7ffa7fff8000", "0x480680017fff8000", "0x1", - "0x48127ffb7fff8000", - "0x48127ffb7fff8000", - "0x208b7fff7fff7ffe", - "0x20780017fff7ffa", - "0x9", - "0x40780017fff7fff", - "0x1", "0x480680017fff8000", "0x0", + "0x20680017fff7ffe", + "0xf", + "0x400280007ffc7fff", + "0x48127ffa7fff8000", + "0x48127ff87fff8000", + "0x48127ffa7fff8000", + "0x48127ffa7fff8000", "0x480a7ffb7fff8000", - "0x480a7ffc7fff8000", - "0x208b7fff7fff7ffe", - "0x40780017fff7fff", - "0x1", - "0x400180007fff7ffd", - "0x480680017fff8000", + "0x482680017ffc8000", "0x1", - "0x48127ffe7fff8000", - "0x482480017ffd8000", + "0x4825800180007ffd", "0x1", + "0x1104800180018000", + "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffc9", "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x9", + "0x48127ffa7fff8000", + "0x48127ff87fff8000", "0x480680017fff8000", "0x0", + "0x48127ff97fff8000", + "0x48127ff97fff8000", + "0x480680017fff8000", + "0x1", "0x480680017fff8000", "0x0", "0x480680017fff8000", "0x0", "0x208b7fff7fff7ffe", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7574206f6620676173", + "0x400080007ffe7fff", + "0x482680017ff78000", + "0x1", + "0x480a7ff87fff8000", "0x480680017fff8000", "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe", - "0x20780017fff7ffb", - "0x8", "0x480680017fff8000", "0x0", "0x480680017fff8000", "0x0", - "0x480a7ffd7fff8000", - "0x208b7fff7fff7ffe", "0x480680017fff8000", + "0x0", + "0x48127ff87fff8000", + "0x482480017ff78000", "0x1", - "0x480a7ffc7fff8000", - "0x480a7ffd7fff8000", "0x208b7fff7fff7ffe" ], + "bytecode_segment_lengths": [ + 258, + 144, + 287, + 334, + 184, + 44, + 92 + ], "hints": [ [ 0, @@ -1585,7 +1362,7 @@ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x154" + "Immediate": "0x0" }, "rhs": { "Deref": { @@ -1602,7 +1379,7 @@ ] ], [ - 43, + 101, [ { "AllocSegment": { @@ -1615,17 +1392,17 @@ ] ], [ - 62, + 120, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x14fa" + "Immediate": "0x0" }, "rhs": { "Deref": { "register": "AP", - "offset": -68 + "offset": -27 } }, "dst": { @@ -1637,7 +1414,7 @@ ] ], [ - 85, + 150, [ { "AllocSegment": { @@ -1650,7 +1427,7 @@ ] ], [ - 108, + 172, [ { "AllocSegment": { @@ -1663,7 +1440,7 @@ ] ], [ - 123, + 187, [ { "AllocSegment": { @@ -1676,7 +1453,7 @@ ] ], [ - 137, + 201, [ { "AllocSegment": { @@ -1689,7 +1466,7 @@ ] ], [ - 151, + 215, [ { "AllocSegment": { @@ -1702,7 +1479,7 @@ ] ], [ - 165, + 229, [ { "AllocSegment": { @@ -1715,7 +1492,7 @@ ] ], [ - 179, + 243, [ { "AllocSegment": { @@ -1728,7 +1505,7 @@ ] ], [ - 194, + 258, [ { "TestLessThanOrEqual": { @@ -1750,7 +1527,7 @@ ] ], [ - 219, + 296, [ { "AllocSegment": { @@ -1763,7 +1540,7 @@ ] ], [ - 238, + 315, [ { "TestLessThanOrEqual": { @@ -1773,7 +1550,7 @@ "rhs": { "Deref": { "register": "AP", - "offset": -23 + "offset": -12 } }, "dst": { @@ -1785,7 +1562,33 @@ ] ], [ - 258, + 336, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 358, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 373, [ { "AllocSegment": { @@ -1798,37 +1601,113 @@ ] ], [ - 281, + 387, [ { "AllocSegment": { "dst": { "register": "AP", - "offset": 0 + "offset": 0 + } + } + } + ] + ], + [ + 402, + [ + { + "TestLessThanOrEqual": { + "lhs": { + "Immediate": "0x0" + }, + "rhs": { + "Deref": { + "register": "FP", + "offset": -6 + } + }, + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 435, + [ + { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "rhs": { + "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + }, + "dst": { + "register": "AP", + "offset": 4 } } } ] ], [ - 296, + 439, [ { - "AllocSegment": { - "dst": { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": 3 + } + }, + "scalar": { + "Immediate": "0x110000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { "register": "AP", - "offset": 0 + "offset": -2 + }, + "y": { + "register": "AP", + "offset": -1 } } } ] ], [ - 310, + 449, [ { - "AllocSegment": { - "dst": { + "LinearSplit": { + "value": { + "Deref": { + "register": "AP", + "offset": -2 + } + }, + "scalar": { + "Immediate": "0x8000000000000000000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -1 + }, + "y": { "register": "AP", "offset": 0 } @@ -1837,19 +1716,10 @@ ] ], [ - 327, + 501, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x1270" - }, - "rhs": { - "Deref": { - "register": "FP", - "offset": -6 - } - }, + "AllocSegment": { "dst": { "register": "AP", "offset": 0 @@ -1859,7 +1729,7 @@ ] ], [ - 370, + 548, [ { "AllocSegment": { @@ -1872,17 +1742,17 @@ ] ], [ - 389, + 567, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x1432" + "Immediate": "0x50a" }, "rhs": { "Deref": { "register": "AP", - "offset": -13 + "offset": -11 } }, "dst": { @@ -1894,7 +1764,7 @@ ] ], [ - 412, + 588, [ { "AllocSegment": { @@ -1907,7 +1777,7 @@ ] ], [ - 435, + 610, [ { "AllocSegment": { @@ -1920,7 +1790,7 @@ ] ], [ - 450, + 625, [ { "AllocSegment": { @@ -1933,7 +1803,7 @@ ] ], [ - 472, + 639, [ { "AllocSegment": { @@ -1946,7 +1816,7 @@ ] ], [ - 486, + 660, [ { "AllocSegment": { @@ -1959,7 +1829,7 @@ ] ], [ - 500, + 674, [ { "AllocSegment": { @@ -1972,12 +1842,12 @@ ] ], [ - 517, + 691, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x1270" + "Immediate": "0x0" }, "rhs": { "Deref": { @@ -1994,46 +1864,78 @@ ] ], [ - 560, + 725, [ { - "AllocSegment": { + "TestLessThan": { + "lhs": { + "Deref": { + "register": "FP", + "offset": 1 + } + }, + "rhs": { + "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + }, "dst": { "register": "AP", - "offset": 0 + "offset": 4 } } } ] ], [ - 579, + 729, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x6fb8" - }, - "rhs": { + "LinearSplit": { + "value": { "Deref": { "register": "AP", - "offset": -13 + "offset": 3 } }, - "dst": { + "scalar": { + "Immediate": "0x110000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { "register": "AP", - "offset": 0 + "offset": -2 + }, + "y": { + "register": "AP", + "offset": -1 } } } ] ], [ - 619, + 739, [ { - "AllocSegment": { - "dst": { + "LinearSplit": { + "value": { + "Deref": { + "register": "FP", + "offset": 1 + } + }, + "scalar": { + "Immediate": "0x8000000000000000000000000000000" + }, + "max_x": { + "Immediate": "0xffffffffffffffffffffffffffffffff" + }, + "x": { + "register": "AP", + "offset": -1 + }, + "y": { "register": "AP", "offset": 0 } @@ -2042,7 +1944,7 @@ ] ], [ - 634, + 792, [ { "AllocSegment": { @@ -2055,7 +1957,7 @@ ] ], [ - 656, + 839, [ { "AllocSegment": { @@ -2068,10 +1970,19 @@ ] ], [ - 670, + 858, [ { - "AllocSegment": { + "TestLessThanOrEqual": { + "lhs": { + "Immediate": "0x54d8" + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -11 + } + }, "dst": { "register": "AP", "offset": 0 @@ -2081,42 +1992,37 @@ ] ], [ - 684, + 876, [ { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 + "SystemCall": { + "system": { + "Deref": { + "register": "FP", + "offset": -5 + } } } } ] ], [ - 699, + 894, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x0" - }, - "rhs": { + "SystemCall": { + "system": { "Deref": { - "register": "FP", - "offset": -6 + "register": "AP", + "offset": -2 } - }, - "dst": { - "register": "AP", - "offset": 0 } } } ] ], [ - 730, + 906, [ { "AllocSegment": { @@ -2129,19 +2035,10 @@ ] ], [ - 749, + 921, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x2a58" - }, - "rhs": { - "Deref": { - "register": "AP", - "offset": -38 - } - }, + "AllocSegment": { "dst": { "register": "AP", "offset": 0 @@ -2151,7 +2048,7 @@ ] ], [ - 771, + 944, [ { "AllocSegment": { @@ -2164,7 +2061,7 @@ ] ], [ - 789, + 959, [ { "AllocSegment": { @@ -2177,7 +2074,7 @@ ] ], [ - 804, + 973, [ { "AllocSegment": { @@ -2190,7 +2087,7 @@ ] ], [ - 818, + 994, [ { "AllocSegment": { @@ -2203,7 +2100,7 @@ ] ], [ - 832, + 1008, [ { "AllocSegment": { @@ -2216,78 +2113,55 @@ ] ], [ - 946, + 1023, [ { - "TestLessThan": { + "TestLessThanOrEqual": { "lhs": { - "Deref": { - "register": "AP", - "offset": -1 - } + "Immediate": "0x0" }, "rhs": { - "Immediate": "0x800000000000000000000000000000000000000000000000000000000000000" + "Deref": { + "register": "FP", + "offset": -6 + } }, "dst": { "register": "AP", - "offset": 4 + "offset": 0 } } } ] ], [ - 950, + 1082, [ { - "LinearSplit": { - "value": { - "Deref": { - "register": "AP", - "offset": 3 - } - }, - "scalar": { - "Immediate": "0x110000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -2 - }, - "y": { + "AllocSegment": { + "dst": { "register": "AP", - "offset": -1 + "offset": 0 } } } ] ], [ - 960, + 1101, [ { - "LinearSplit": { - "value": { + "TestLessThanOrEqual": { + "lhs": { + "Immediate": "0x134c" + }, + "rhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -17 } }, - "scalar": { - "Immediate": "0x8000000000000000000000000000000" - }, - "max_x": { - "Immediate": "0xffffffffffffffffffffffffffffffff" - }, - "x": { - "register": "AP", - "offset": -1 - }, - "y": { + "dst": { "register": "AP", "offset": 0 } @@ -2296,35 +2170,35 @@ ] ], [ - 1020, + 1126, [ { - "AllocSegment": { - "dst": { - "register": "AP", - "offset": 0 + "SystemCall": { + "system": { + "Deref": { + "register": "FP", + "offset": -5 + } } } } ] ], [ - 1111, + 1129, [ { - "SystemCall": { - "system": { - "Deref": { - "register": "AP", - "offset": -8 - } + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1150, + 1149, [ { "AllocSegment": { @@ -2337,19 +2211,10 @@ ] ], [ - 1224, + 1164, [ { - "TestLessThanOrEqual": { - "lhs": { - "Immediate": "0x12a2" - }, - "rhs": { - "Deref": { - "register": "FP", - "offset": -8 - } - }, + "AllocSegment": { "dst": { "register": "AP", "offset": 0 @@ -2359,7 +2224,7 @@ ] ], [ - 1307, + 1178, [ { "AllocSegment": { @@ -2372,27 +2237,25 @@ ] ], [ - 1378, + 1192, [ { - "SystemCall": { - "system": { - "Deref": { - "register": "FP", - "offset": -4 - } + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1424, + 1207, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x816" + "Immediate": "0x42e" }, "rhs": { "Deref": { @@ -2409,7 +2272,7 @@ ] ], [ - 1473, + 1237, [ { "AllocSegment": { @@ -2422,22 +2285,29 @@ ] ], [ - 1491, + 1251, [ { - "SystemCall": { - "system": { + "TestLessThanOrEqual": { + "lhs": { + "Immediate": "0x942" + }, + "rhs": { "Deref": { "register": "FP", - "offset": -3 + "offset": -8 } + }, + "dst": { + "register": "AP", + "offset": 0 } } } ] ], [ - 1539, + 1323, [ { "AllocSegment": { @@ -2454,21 +2324,21 @@ "EXTERNAL": [ { "selector": "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad", - "offset": 515, + "offset": 689, "builtins": [ "range_check" ] }, { "selector": "0x162da33a4585851fe8d3af3c2a9c60b557814e221e0d4f30ff0b2189d9c7775", - "offset": 325, + "offset": 402, "builtins": [ "range_check" ] }, { "selector": "0x289da278a8dc833409cabfdad1581e8e7d40e42dcaed693fa4008dcdb4963b3", - "offset": 194, + "offset": 258, "builtins": [ "range_check" ] @@ -2485,7 +2355,7 @@ "CONSTRUCTOR": [ { "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "offset": 699, + "offset": 1023, "builtins": [ "range_check" ] diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/empty_contract.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/empty_contract.casm.json index 3fc4dd780ad..45030bbbd7b 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/empty_contract.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/empty_contract.casm.json @@ -1,7 +1,8 @@ { "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "compiler_version": "2.4.0", + "compiler_version": "2.6.0", "bytecode": [], + "bytecode_segment_lengths": 0, "hints": [], "entry_points_by_type": { "EXTERNAL": [], diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/legacy_test_contract.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/legacy_test_contract.casm.json index 211628dc362..891d286b887 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/legacy_test_contract.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/legacy_test_contract.casm.json @@ -1486,4 +1486,4 @@ "L1_HANDLER": [], "CONSTRUCTOR": [] } -} \ No newline at end of file +} diff --git a/crates/blockifier/feature_contracts/cairo1/compiled/test_contract.casm.json b/crates/blockifier/feature_contracts/cairo1/compiled/test_contract.casm.json index 61d5278f7e1..8b5a51c976c 100644 --- a/crates/blockifier/feature_contracts/cairo1/compiled/test_contract.casm.json +++ b/crates/blockifier/feature_contracts/cairo1/compiled/test_contract.casm.json @@ -1,6 +1,6 @@ { "prime": "0x800000000000011000000000000000000000000000000000000000000000001", - "compiler_version": "2.6.3", + "compiler_version": "2.6.0", "bytecode": [ "0xa0680017fff8000", "0x7", @@ -100,9 +100,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x39f0", + "0x3aaf", "0x482480017fff8000", - "0x39ef", + "0x3aae", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -275,9 +275,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x3941", + "0x3a00", "0x482480017fff8000", - "0x3940", + "0x39ff", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -588,9 +588,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x3808", + "0x38c7", "0x482480017fff8000", - "0x3807", + "0x38c6", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -922,9 +922,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x36ba", + "0x3779", "0x482480017fff8000", - "0x36b9", + "0x3778", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -1128,9 +1128,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x35ec", + "0x36ab", "0x482480017fff8000", - "0x35eb", + "0x36aa", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -1344,9 +1344,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x3514", + "0x35d3", "0x482480017fff8000", - "0x3513", + "0x35d2", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -1682,9 +1682,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x33c2", + "0x3481", "0x482480017fff8000", - "0x33c1", + "0x3480", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -1969,9 +1969,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x32a3", + "0x3362", "0x482480017fff8000", - "0x32a2", + "0x3361", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -2186,9 +2186,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x31ca", + "0x3289", "0x482480017fff8000", - "0x31c9", + "0x3288", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -2402,9 +2402,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x30f2", + "0x31b1", "0x482480017fff8000", - "0x30f1", + "0x31b0", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -2541,9 +2541,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x3067", + "0x3126", "0x482480017fff8000", - "0x3066", + "0x3125", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -2814,9 +2814,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2f56", + "0x3015", "0x482480017fff8000", - "0x2f55", + "0x3014", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -2992,21 +2992,21 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2ea4", + "0x2f63", "0x482480017fff8000", - "0x2ea3", + "0x2f62", "0x480080007fff8000", "0xa0680017fff8000", "0x9", "0x4824800180007ff8", - "0x7b3e", + "0x7ba2", "0x482480017fff8000", "0x100000000000000000000000000000000", "0x400080007ff77fff", "0x10780017fff7fff", "0x1f", "0x4824800180007ff8", - "0x7b3e", + "0x7ba2", "0x400080007ff87fff", "0x482480017ff88000", "0x1", @@ -3097,15 +3097,15 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2e3b", + "0x2efa", "0x482480017fff8000", - "0x2e3a", + "0x2ef9", "0x480080007fff8000", "0x480080017fff8000", "0x484480017fff8000", "0x8", "0x482480017fff8000", - "0x41906", + "0x41c26", "0xa0680017fff8000", "0x8", "0x48307ffe80007ff5", @@ -3209,28 +3209,28 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2dcb", + "0x2e8a", "0x482480017fff8000", - "0x2dca", + "0x2e89", "0x480080007fff8000", "0xa0680017fff8000", "0x9", "0x4824800180007ff8", - "0x382d4", + "0x38400", "0x482480017fff8000", "0x100000000000000000000000000000000", "0x400080007ff77fff", "0x10780017fff7fff", "0x1f", "0x4824800180007ff8", - "0x382d4", + "0x38400", "0x400080007ff87fff", "0x482480017ff88000", "0x1", "0x48127ffe7fff8000", "0x480a7ffb7fff8000", "0x1104800180018000", - "0x154c", + "0x155d", "0x20680017fff7ffd", "0xc", "0x40780017fff7fff", @@ -3355,9 +3355,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2d39", + "0x2df8", "0x482480017fff8000", - "0x2d38", + "0x2df7", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -3557,9 +3557,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2c6f", + "0x2d2e", "0x482480017fff8000", - "0x2c6e", + "0x2d2d", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -3580,7 +3580,7 @@ "0x48127ff47fff8000", "0x48127ff47fff8000", "0x1104800180018000", - "0x1577", + "0x159a", "0x20680017fff7ffd", "0xe", "0x40780017fff7fff", @@ -3679,9 +3679,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2bf5", + "0x2cb4", "0x482480017fff8000", - "0x2bf4", + "0x2cb3", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -3793,9 +3793,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2b83", + "0x2c42", "0x482480017fff8000", - "0x2b82", + "0x2c41", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -3814,7 +3814,7 @@ "0x48127ffe7fff8000", "0x48127ff67fff8000", "0x1104800180018000", - "0x15dc", + "0x1618", "0x20680017fff7ffd", "0xc", "0x40780017fff7fff", @@ -3932,9 +3932,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2af8", + "0x2bb7", "0x482480017fff8000", - "0x2af7", + "0x2bb6", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -3953,7 +3953,7 @@ "0x48127ffe7fff8000", "0x48127ff67fff8000", "0x1104800180018000", - "0x1581", + "0x15bd", "0x20680017fff7ffd", "0xc", "0x40780017fff7fff", @@ -4137,9 +4137,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2a2b", + "0x2aea", "0x482480017fff8000", - "0x2a2a", + "0x2ae9", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -4381,9 +4381,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2937", + "0x29f6", "0x482480017fff8000", - "0x2936", + "0x29f5", "0x480080007fff8000", "0x480080007fff8000", "0x484480017fff8000", @@ -4409,7 +4409,7 @@ "0x48127feb7fff8000", "0x48127fef7fff8000", "0x1104800180018000", - "0x13e5", + "0x1421", "0x20680017fff7ffd", "0xd", "0x40780017fff7fff", @@ -4524,7 +4524,7 @@ "0x480a7ffc7fff8000", "0x480a7ffd7fff8000", "0x1104800180018000", - "0x1435", + "0x1471", "0x20680017fff7ffc", "0x63", "0x48307ffa80007ffb", @@ -4549,9 +4549,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x288f", + "0x294e", "0x482480017fff8000", - "0x288e", + "0x294d", "0x480080007fff8000", "0x480080007fff8000", "0x484480017fff8000", @@ -4582,7 +4582,7 @@ "0x48127feb7fff8000", "0x48127feb7fff8000", "0x1104800180018000", - "0x14bf", + "0x14fb", "0x20680017fff7ffd", "0xe", "0x40780017fff7fff", @@ -4717,7 +4717,7 @@ "0x48127ff67fff8000", "0x48127ff67fff8000", "0x1104800180018000", - "0x1374", + "0x13b0", "0x20680017fff7ffc", "0x60", "0x48307ffa80007ffb", @@ -4740,9 +4740,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x27d0", + "0x288f", "0x482480017fff8000", - "0x27cf", + "0x288e", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -4897,9 +4897,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2733", + "0x27f2", "0x482480017fff8000", - "0x2732", + "0x27f1", "0x480080007fff8000", "0x480080027fff8000", "0x482480017fff8000", @@ -4918,7 +4918,7 @@ "0x48127ffe7fff8000", "0x480a7ffb7fff8000", "0x1104800180018000", - "0x1491", + "0x14cd", "0x482480017f838000", "0x1", "0x20680017fff7ffc", @@ -5029,9 +5029,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x26af", + "0x276e", "0x482480017fff8000", - "0x26ae", + "0x276d", "0x480080007fff8000", "0x480080007fff8000", "0x484480017fff8000", @@ -5055,7 +5055,7 @@ "0x480a7ffb7fff8000", "0x48127ff17fff8000", "0x1104800180018000", - "0x1525", + "0x1561", "0x20680017fff7ffd", "0xd", "0x40780017fff7fff", @@ -5178,9 +5178,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x261a", + "0x26d9", "0x482480017fff8000", - "0x2619", + "0x26d8", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -5355,9 +5355,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x2569", + "0x2628", "0x482480017fff8000", - "0x2568", + "0x2627", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -5561,9 +5561,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x249b", + "0x255a", "0x482480017fff8000", - "0x249a", + "0x2559", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -5766,9 +5766,9 @@ "0x1", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x23ce", + "0x248d", "0x482480017fff8000", - "0x23cd", + "0x248c", "0x480080007fff8000", "0xa0680017fff8000", "0x9", @@ -6402,7 +6402,7 @@ "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x1104800180018000", - "0x1110", + "0x114c", "0x20680017fff7ffa", "0x384", "0x20680017fff7ffd", @@ -6506,7 +6506,7 @@ "0x48127ffa7fff8000", "0x480080007ff88000", "0x1104800180018000", - "0x1161", + "0x119d", "0x20680017fff7ffa", "0x1a", "0x20680017fff7ffd", @@ -6638,7 +6638,7 @@ "0x48127ff97fff8000", "0x48127ff97fff8000", "0x1104800180018000", - "0x1024", + "0x1060", "0x20680017fff7ffa", "0x165", "0x20680017fff7ffd", @@ -6738,7 +6738,7 @@ "0x48127ff87fff8000", "0x48127ff87fff8000", "0x1104800180018000", - "0xfc0", + "0xffc", "0x20680017fff7ffa", "0x49", "0x20680017fff7ffd", @@ -7577,7 +7577,7 @@ "0x480a7fed7fff8000", "0x480a7fee7fff8000", "0x1104800180018000", - "0xd82", + "0xdbe", "0x20680017fff7ffa", "0xdc", "0x20680017fff7fff", @@ -7617,7 +7617,7 @@ "0x480a7ff27fff8000", "0x480a7ff37fff8000", "0x1104800180018000", - "0xddb", + "0xe17", "0x20680017fff7ffa", "0xa2", "0x20680017fff7fff", @@ -7653,7 +7653,7 @@ "0x480a7ff57fff8000", "0x480a7ff67fff8000", "0x1104800180018000", - "0xd36", + "0xd72", "0x20680017fff7ffa", "0x78", "0x20680017fff7fff", @@ -7698,7 +7698,7 @@ "0x480a7ff97fff8000", "0x480a7ffa7fff8000", "0x1104800180018000", - "0xd09", + "0xd45", "0x20680017fff7ffa", "0x45", "0x20680017fff7fff", @@ -8135,7 +8135,7 @@ "0x48127ffb7fff8000", "0x48127ffa7fff8000", "0x1104800180018000", - "0xc6c", + "0xca8", "0x20680017fff7ffb", "0xb4", "0x48127ff97fff8000", @@ -8147,7 +8147,7 @@ "0x480680017fff8000", "0x0", "0x1104800180018000", - "0xcb9", + "0xcf5", "0x20680017fff7ffd", "0xa1", "0x480680017fff8000", @@ -8347,7 +8347,7 @@ "0x400280057ffd7ffe", "0x480280077ffd8000", "0x20680017fff7fff", - "0x145", + "0x156", "0x480280087ffd8000", "0x480280097ffd8000", "0x480280067ffd8000", @@ -8418,7 +8418,7 @@ "0x48307ffe80007ffb", "0x400280007ffa7fff", "0x10780017fff7fff", - "0xee", + "0xff", "0x482480017ffb8000", "0x1", "0x48307fff80007ffd", @@ -8430,7 +8430,7 @@ "0x482680017ffa8000", "0x1", "0x20680017fff7ffe", - "0xd3", + "0xe4", "0x480680017fff8000", "0xe3e70682c2094cac629f6fbed82c07cd", "0x480680017fff8000", @@ -8449,14 +8449,14 @@ "0x400080057ff27ffe", "0x480080077ff28000", "0x20680017fff7fff", - "0xb6", + "0xc7", "0x480080087ff18000", "0x480080097ff08000", "0x480080067fef8000", "0x482480017fee8000", "0xa", "0x20680017fff7ffc", - "0xa0", + "0xb1", "0x480680017fff8000", "0x536563703235366b314765745879", "0x400080007ffe7fff", @@ -8464,7 +8464,7 @@ "0x400080027ffe7ffc", "0x480080047ffe8000", "0x20680017fff7fff", - "0x8e", + "0x9f", "0x480080057ffd8000", "0x480080067ffc8000", "0x480080037ffb8000", @@ -8524,26 +8524,43 @@ "0x1", "0x208b7fff7fff7ffe", "0x480680017fff8000", - "0x767410c1", - "0x480680017fff8000", "0x100000000", + "0x20680017fff7fff", + "0x11", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4469766973696f6e2062792030", + "0x400080007ffe7fff", + "0x48127fe67fff8000", + "0x48127ff47fff8000", + "0x480a7ffc7fff8000", + "0x48127ff37fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ff97fff8000", + "0x482480017ff88000", + "0x1", + "0x208b7fff7fff7ffe", + "0x480680017fff8000", + "0x767410c1", "0x480080007fe78005", "0x480080017fe68005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", + "0x48307ffd7ffe7ffb", "0x480080027fe37ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff87ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", "0xffffffffffffffff0000000000000000", "0x400080037fe07ffc", - "0x40507ffe7ff87ffd", - "0x40307fff7ffd7ff7", + "0x40507ffe7ff77ffd", + "0x40307fff7ffd7ff8", "0x484480017fff8000", "0x100000000000000000000000000000000", "0x482480017fdf8000", @@ -8568,7 +8585,7 @@ "0x482480017ff48000", "0xbb448978bd42b984d7de5970bcaf5c43", "0x1104800180018000", - "0xc46", + "0xc80", "0x20680017fff7ffd", "0x17", "0x20680017fff7ffe", @@ -8699,7 +8716,7 @@ "0x400280057ffd7ffe", "0x480280077ffd8000", "0x20680017fff7fff", - "0x16d", + "0x17f", "0x480280087ffd8000", "0x480280097ffd8000", "0x480280067ffd8000", @@ -8772,7 +8789,7 @@ "0x48307ffe80007ffb", "0x400280007ffb7fff", "0x10780017fff7fff", - "0x113", + "0x125", "0x482480017ffb8000", "0x1", "0x48307fff80007ffd", @@ -8784,7 +8801,7 @@ "0x482680017ffb8000", "0x1", "0x20680017fff7ffe", - "0xf7", + "0x109", "0x480680017fff8000", "0x2d483fe223b12b91047d83258a958b0f", "0x480680017fff8000", @@ -8803,14 +8820,14 @@ "0x400080057ff27ffe", "0x480080077ff28000", "0x20680017fff7fff", - "0xd9", + "0xeb", "0x480080087ff18000", "0x480080097ff08000", "0x480080067fef8000", "0x482480017fee8000", "0xa", "0x20680017fff7ffc", - "0xc2", + "0xd4", "0x480680017fff8000", "0x5365637032353672314765745879", "0x400080007ffe7fff", @@ -8818,7 +8835,7 @@ "0x400080027ffe7ffc", "0x480080047ffe8000", "0x20680017fff7fff", - "0xaf", + "0xc1", "0x480080057ffd8000", "0x480080067ffc8000", "0x480080037ffb8000", @@ -8879,26 +8896,44 @@ "0x1", "0x208b7fff7fff7ffe", "0x480680017fff8000", - "0x49288242", - "0x480680017fff8000", "0x100000000", + "0x20680017fff7fff", + "0x12", + "0x40780017fff7fff", + "0x2fd", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4469766973696f6e2062792030", + "0x400080007ffe7fff", + "0x48127ce97fff8000", + "0x48127cf77fff8000", + "0x48127cf77fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", + "0x480680017fff8000", + "0x49288242", "0x480080007fe78005", "0x480080017fe68005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", + "0x48307ffd7ffe7ffb", "0x480080027fe37ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff87ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", "0xffffffffffffffff0000000000000000", "0x400080037fe07ffc", - "0x40507ffe7ff87ffd", - "0x40307fff7ffd7ff7", + "0x40507ffe7ff77ffd", + "0x40307fff7ffd7ff8", "0x480680017fff8000", "0x32e41495a944d0045b522eba7240fad5", "0x480680017fff8000", @@ -8944,7 +8979,7 @@ "0x177e60492c5a8242f76f07bfe3661bd", "0x48127ff47fff8000", "0x1104800180018000", - "0xbaa", + "0xbd2", "0x20680017fff7ffd", "0xc", "0x48127ffa7fff8000", @@ -9080,7 +9115,7 @@ "0xffffffffffffffffffffffffffffc57c", "0x400280007ff97fff", "0x10780017fff7fff", - "0x13b", + "0x154", "0x4825800180007ffa", "0x3a84", "0x400280007ff97fff", @@ -9379,14 +9414,19 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x480680017fff8000", + "0x0", + "0x482680017ff98000", + "0x2", + "0x20680017fff7ffe", + "0x10", "0x40780017fff7fff", "0x1", "0x480680017fff8000", "0x4469766973696f6e2062792030", "0x400080007ffe7fff", - "0x482680017ff98000", - "0x2", - "0x48127ff77fff8000", + "0x48127ffd7fff8000", + "0x48127ff57fff8000", "0x480a7ffb7fff8000", "0x480680017fff8000", "0x1", @@ -9394,6 +9434,26 @@ "0x482480017ff98000", "0x1", "0x208b7fff7fff7ffe", + "0x480680017fff8000", + "0x1", + "0x480080007ffe8004", + "0x4824800180037fff", + "0x1", + "0x48307ffe7fff7ffb", + "0x480080017ffb7ffe", + "0x480080027ffa7fff", + "0x40507ffe7ff87ffd", + "0x40307fff7ffd7ffa", + "0x482480017ff98000", + "0x3", + "0x48127ff17fff8000", + "0x480a7ffb7fff8000", + "0x480680017fff8000", + "0x0", + "0x480680017fff8000", + "0x0", + "0x48127ff97fff8000", + "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", "0x480680017fff8000", @@ -10983,7 +11043,7 @@ "0x480a7ff97fff8000", "0x480a7ffa7fff8000", "0x1104800180018000", - "0x6ca", + "0x6e9", "0x20680017fff7ffc", "0x11", "0x400280007ffc7ffd", @@ -11317,12 +11377,12 @@ "0xa0680017fff8000", "0x7", "0x482680017ff98000", - "0xffffffffffffffffffffffffffffdd32", + "0xffffffffffffffffffffffffffffdc06", "0x400280007ff87fff", "0x10780017fff7fff", "0x42", "0x4825800180007ff9", - "0x22ce", + "0x23fa", "0x400280007ff87fff", "0x482680017ff88000", "0x1", @@ -11353,20 +11413,20 @@ "0x480080007ffc8000", "0x480080017ffb8000", "0x1104800180018000", - "0x608", + "0x627", "0x20680017fff7ffd", "0xb", "0x48127ffc7fff8000", - "0x48127fce7fff8000", - "0x48127fd07fff8000", - "0x48127fd07fff8000", + "0x48127fcd7fff8000", + "0x48127fcf7fff8000", + "0x48127fcf7fff8000", "0x48127ffa7fff8000", "0x48127ffa7fff8000", "0x1104800180018000", "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffd1", "0x208b7fff7fff7ffe", "0x48127ffc7fff8000", - "0x48127fce7fff8000", + "0x48127fcd7fff8000", "0x480680017fff8000", "0x1", "0x480680017fff8000", @@ -11403,17 +11463,32 @@ "0x482480017ff88000", "0x1", "0x208b7fff7fff7ffe", - "0x48297ffa80007ffb", "0x480680017fff8000", "0x11", + "0x20680017fff7fff", + "0xf", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x480a7ff87fff8000", + "0x480a7ff97fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffb7fff8000", + "0x482480017ffa8000", + "0x1", + "0x208b7fff7fff7ffe", + "0x48297ffa80007ffb", "0x480280007ff88004", "0x4824800180037fff", "0x1", - "0x48307ffe7fff7ffd", + "0x48307ffe7fff7ffc", "0x480280017ff87ffe", "0x480280027ff87fff", - "0x40507ffe7ffa7ffd", - "0x40307fff7ffd7ff9", + "0x40507ffe7ff97ffd", + "0x40307fff7ffd7ffa", "0x482680017ff88000", "0x3", "0x4825800180007ffd", @@ -11627,7 +11702,7 @@ "0x48127ff87fff8000", "0x48127ffb7fff8000", "0x1104800180018000", - "0x593", + "0x5c3", "0x208b7fff7fff7ffe", "0x40780017fff7fff", "0x1", @@ -11835,7 +11910,7 @@ "0x480a7ffb7fff8000", "0x480a7ffc7fff8000", "0x1104800180018000", - "0x516", + "0x546", "0x20680017fff7ffd", "0x3e", "0x20680017fff7ffe", @@ -11846,7 +11921,7 @@ "0x48127ff97fff8000", "0x48127ffb7fff8000", "0x1104800180018000", - "0x7cd", + "0x80d", "0x20680017fff7ffd", "0x1b", "0x48317fff80007ffd", @@ -11944,7 +12019,7 @@ "0x20680017fff7fff", "0x4", "0x10780017fff7fff", - "0x2fd", + "0x30d", "0x480680017fff8000", "0xffffffff00000000ffffffffffffffff", "0x48317fff80017ffa", @@ -11968,7 +12043,7 @@ "0x2d3", "0x48127d2b7fff8000", "0x10780017fff7fff", - "0x2e8", + "0x2f8", "0x480680017fff8000", "0xbce6faada7179e84f3b9cac2fc632551", "0x48317fff80017ff9", @@ -11985,7 +12060,7 @@ "0x482480017d2b8000", "0x1", "0x10780017fff7fff", - "0x2d7", + "0x2e7", "0x482480017ffa8000", "0x1", "0x10780017fff7fff", @@ -12078,11 +12153,31 @@ "0x1", "0x48307ffe80007fff", "0x20680017fff7fff", - "0x272", + "0x282", "0x480680017fff8000", "0xbce6faada7179e84f3b9cac2fc632551", "0x480680017fff8000", "0xffffffff00000000ffffffffffffffff", + "0x20680017fff7ffe", + "0x14", + "0x20680017fff7fff", + "0x12", + "0x40780017fff7fff", + "0x2bb", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x48127d3d7fff8000", + "0x480a7ff57fff8000", + "0x480a7ff67fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", "0xa0680017fff8000", "0x37", "0x480080007ff98001", @@ -12184,7 +12279,7 @@ "0x40317ff97ffb7ffc", "0x40307ffa7ffc7ff1", "0x10780017fff7fff", - "0x1bb", + "0x1b7", "0x4824800180008002", "0xffffffffffffffff0000000000000000", "0x480080097fcb8001", @@ -12431,23 +12526,19 @@ "0x480a7ff87fff8000", "0x48127f597fff8000", "0x48127f597fff8000", - "0x480680017fff8000", - "0xbce6faada7179e84f3b9cac2fc632551", - "0x480680017fff8000", - "0xffffffff00000000ffffffffffffffff", + "0x48127f537fff8000", + "0x48127f537fff8000", "0x1104800180018000", - "0x648", + "0x68a", "0x48127ffd7fff8000", "0x480a7ff97fff8000", "0x480a7ffa7fff8000", "0x48127e5d7fff8000", "0x48127e5d7fff8000", - "0x480680017fff8000", - "0xbce6faada7179e84f3b9cac2fc632551", - "0x480680017fff8000", - "0xffffffff00000000ffffffffffffffff", + "0x48127e577fff8000", + "0x48127e577fff8000", "0x1104800180018000", - "0x63d", + "0x681", "0x480680017fff8000", "0x77037d812deb33a0f4a13945d898c296", "0x480680017fff8000", @@ -12898,6 +12989,21 @@ "0x208b7fff7fff7ffe", "0x480680017fff8000", "0x10000000000000000", + "0x20680017fff7fff", + "0xf", + "0x40780017fff7fff", + "0x1a", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x480a7ff97fff8000", + "0x48127ffd7fff8000", + "0x482480017ffc8000", + "0x1", + "0x10780017fff7fff", + "0xa8", "0x480280007ff98005", "0x480280017ff98005", "0x4824800180047ffe", @@ -12921,7 +13027,7 @@ "0x10000000000000000", "0x400280047ff97fff", "0x10780017fff7fff", - "0x73", + "0x84", "0x482480017ffd8000", "0xffffffffffffffff0000000000000000", "0x400280047ff97fff", @@ -12931,7 +13037,7 @@ "0x10000000000000000", "0x400280057ff97fff", "0x10780017fff7fff", - "0x5b", + "0x6c", "0x482480017ffc8000", "0xffffffffffffffff0000000000000000", "0x400280057ff97fff", @@ -12939,56 +13045,73 @@ "0x400280017ffb7ffa", "0x480680017fff8000", "0x10000000000000000", - "0x480280067ff98005", - "0x480280077ff98005", + "0x482680017ff98000", + "0x6", + "0x480a7ffa7fff8000", + "0x482680017ffb8000", + "0x2", + "0x20680017fff7ffc", + "0xf", + "0x40780017fff7fff", + "0xb", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x48127ff07fff8000", + "0x48127ffd7fff8000", + "0x482480017ffc8000", + "0x1", + "0x10780017fff7fff", + "0x4b", + "0x480080007ffd8005", + "0x480080017ffc8005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", - "0x480280087ff97ffd", + "0x48307ffd7ffe7ff9", + "0x480080027ff97ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff67ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", "0xffffffffffffffff0000000000000000", - "0x400280097ff97ffc", - "0x40507ffe7ff87ffd", + "0x400080037ff67ffc", + "0x40507ffe7ff57ffd", "0x40317fff7ffd7ffd", - "0x480a7ffa7fff8000", - "0x482680017ffb8000", - "0x2", "0xa0680017fff8000", "0x7", - "0x4824800180007ffb", + "0x4824800180007ffd", "0x10000000000000000", - "0x4002800a7ff97fff", + "0x400080047ff47fff", "0x10780017fff7fff", "0x27", - "0x482480017ffb8000", + "0x482480017ffd8000", "0xffffffffffffffff0000000000000000", - "0x4002800a7ff97fff", + "0x400080047ff47fff", "0xa0680017fff8000", "0x7", - "0x4824800180007ffa", + "0x4824800180007ffc", "0x10000000000000000", - "0x4002800b7ff97fff", + "0x400080057ff27fff", "0x10780017fff7fff", "0x11", - "0x482480017ffa8000", + "0x482480017ffc8000", "0xffffffffffffffff0000000000000000", - "0x4002800b7ff97fff", + "0x400080057ff27fff", "0x40780017fff7fff", "0x5", - "0x400080007ff67ff4", - "0x400080017ff67ff3", - "0x482680017ff98000", - "0xc", + "0x400080007fef7ff6", + "0x400080017fef7ff5", + "0x482480017fed8000", + "0x6", "0x480680017fff8000", "0x0", - "0x48127ff37fff8000", - "0x482480017ff38000", + "0x48127fec7fff8000", + "0x482480017fec8000", "0x2", "0x208b7fff7fff7ffe", "0x40780017fff7fff", @@ -12996,8 +13119,8 @@ "0x480680017fff8000", "0x4f7074696f6e3a3a756e77726170206661696c65642e", "0x400080007ffe7fff", - "0x482680017ff98000", - "0xc", + "0x482480017ff08000", + "0x6", "0x48127ffd7fff8000", "0x482480017ffc8000", "0x1", @@ -13010,8 +13133,8 @@ "0x480680017fff8000", "0x4f7074696f6e3a3a756e77726170206661696c65642e", "0x400080007ffe7fff", - "0x482680017ff98000", - "0xb", + "0x482480017ff08000", + "0x5", "0x48127ffd7fff8000", "0x482480017ffc8000", "0x1", @@ -13022,7 +13145,7 @@ "0x48127ffc7fff8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", - "0xe", + "0xf", "0x40780017fff7fff", "0x1", "0x480680017fff8000", @@ -13036,7 +13159,7 @@ "0x10780017fff7fff", "0xe", "0x40780017fff7fff", - "0x10", + "0x11", "0x40780017fff7fff", "0x1", "0x480680017fff8000", @@ -13145,14 +13268,14 @@ "0x400380047ff67ffd", "0x480280067ff68000", "0x20680017fff7fff", - "0x2af", + "0x2bf", "0x480280077ff68000", "0x480280087ff68000", "0x480280057ff68000", "0x482680017ff68000", "0x9", "0x20680017fff7ffc", - "0x29c", + "0x2ac", "0x480680017fff8000", "0x29bfcdb2dce28d959f2815b16f81798", "0x480680017fff8000", @@ -13171,18 +13294,38 @@ "0x400080057ffa7ffe", "0x480080077ffa8000", "0x20680017fff7fff", - "0x27a", + "0x28a", "0x480080087ff98000", "0x480080097ff88000", "0x480080067ff78000", "0x482480017ff68000", "0xa", "0x20680017fff7ffc", - "0x265", + "0x275", "0x480680017fff8000", "0xbaaedce6af48a03bbfd25e8cd0364141", "0x480680017fff8000", "0xfffffffffffffffffffffffffffffffe", + "0x20680017fff7ffe", + "0x14", + "0x20680017fff7fff", + "0x12", + "0x40780017fff7fff", + "0x2bb", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x480a7ff47fff8000", + "0x48127d3e7fff8000", + "0x48127d3e7fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ffa7fff8000", + "0x482480017ff98000", + "0x1", + "0x208b7fff7fff7ffe", "0xa0680017fff8000", "0x37", "0x480280007ff48001", @@ -13284,7 +13427,7 @@ "0x40317ff97ffb7ffa", "0x40307ffa7ffc7ff1", "0x10780017fff7fff", - "0x1ae", + "0x1aa", "0x4824800180008002", "0xffffffffffffffff0000000000000000", "0x480280097ff48001", @@ -13531,12 +13674,10 @@ "0x480a7ff87fff8000", "0x48127f597fff8000", "0x48127f597fff8000", - "0x480680017fff8000", - "0xbaaedce6af48a03bbfd25e8cd0364141", - "0x480680017fff8000", - "0xfffffffffffffffffffffffffffffffe", + "0x48127f537fff8000", + "0x48127f537fff8000", "0x1104800180018000", - "0x1fc", + "0x20e", "0x480680017fff8000", "0xfffffffffffffffffffffffffffffffe", "0x48307ffe80017fff", @@ -13609,18 +13750,16 @@ "0x480680017fff8000", "0x1", "0x20680017fff7fff", - "0x59", + "0x57", "0x48127ffc7fff8000", "0x480a7ffb7fff8000", "0x480a7ffc7fff8000", "0x48127e4a7fff8000", "0x48127e4a7fff8000", - "0x480680017fff8000", - "0xbaaedce6af48a03bbfd25e8cd0364141", - "0x480680017fff8000", - "0xfffffffffffffffffffffffffffffffe", + "0x48127e447fff8000", + "0x48127e447fff8000", "0x1104800180018000", - "0x1a8", + "0x1bc", "0x48127f017fff8000", "0x48127f017fff8000", "0x480680017fff8000", @@ -13851,7 +13990,7 @@ "0x400380027ffc7ffd", "0x480280047ffc8000", "0x20680017fff7fff", - "0xb7", + "0xcb", "0x40780017fff7fff", "0x1", "0x480280057ffc8000", @@ -13872,13 +14011,13 @@ "0x4", "0x48127ffa7fff8000", "0x48127ff97fff8000", - "0x402780017ffc8001", + "0x402780017ffc8000", "0x9", "0x1104800180018000", - "0x2d1", - "0x40137ffa7fff8000", + "0x2e5", + "0x40137ffa7fff8001", "0x20680017fff7ffb", - "0x8e", + "0xa2", "0x48127ff87fff8000", "0x48127ff87fff8000", "0x48127ffc7fff8000", @@ -13888,145 +14027,165 @@ "0x480680017fff8000", "0x0", "0x1104800180018000", - "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffff64d", + "0x800000000000010fffffffffffffffffffffffffffffffffffffffffffff5fe", "0x20680017fff7ffd", - "0x7b", + "0x8f", "0x480680017fff8000", "0x4b656363616b", - "0x4002800080017fff", - "0x4002800180017ffb", - "0x4002800280017ffd", - "0x4002800380017ffe", - "0x4802800580018000", - "0x20680017fff7fff", - "0x6a", - "0x4802800780018000", "0x4002800080007fff", + "0x4002800180007ffb", + "0x4002800280007ffd", + "0x4002800380007ffe", + "0x4802800580008000", + "0x20680017fff7fff", + "0x7e", + "0x4802800780008000", + "0x4002800080017fff", "0x480680017fff8000", "0xff00ff00ff00ff00ff00ff00ff00ff", - "0x4002800180007fff", - "0x4802800280008000", + "0x4002800180017fff", + "0x4802800280018000", "0x484480017fff8000", "0xffff", "0x48307fff7ffc8000", - "0x4002800580007fff", + "0x4002800580017fff", "0x480680017fff8000", "0xffff0000ffff0000ffff0000ffff00", - "0x4002800680007fff", - "0x4802800780008000", + "0x4002800680017fff", + "0x4802800780018000", "0x484480017fff8000", "0xffffffff", "0x48307fff7ffc8000", - "0x4002800a80007fff", + "0x4002800a80017fff", "0x480680017fff8000", "0xffffffff00000000ffffffff000000", - "0x4002800b80007fff", - "0x4802800c80008000", + "0x4002800b80017fff", + "0x4802800c80018000", "0x484480017fff8000", "0xffffffffffffffff", "0x48307fff7ffc8000", - "0x4002800f80007fff", + "0x4002800f80017fff", "0x480680017fff8000", "0xffffffffffffffff00000000000000", - "0x4002801080007fff", - "0x4802801180008000", + "0x4002801080017fff", + "0x4802801180018000", "0x484480017fff8000", "0xffffffffffffffffffffffffffffffff", "0x48307fff7ffc8000", - "0x4802800680018000", - "0x4002801480007fff", + "0x4802800680008000", + "0x4002801480017fff", "0x480680017fff8000", "0xff00ff00ff00ff00ff00ff00ff00ff", - "0x4002801580007fff", - "0x4802801680008000", + "0x4002801580017fff", + "0x4802801680018000", "0x484480017fff8000", "0xffff", "0x48307fff7ffc8000", - "0x4002801980007fff", + "0x4002801980017fff", "0x480680017fff8000", "0xffff0000ffff0000ffff0000ffff00", - "0x4002801a80007fff", - "0x4802801b80008000", + "0x4002801a80017fff", + "0x4802801b80018000", "0x484480017fff8000", "0xffffffff", "0x48307fff7ffc8000", - "0x4002801e80007fff", + "0x4002801e80017fff", "0x480680017fff8000", "0xffffffff00000000ffffffff000000", - "0x4002801f80007fff", - "0x4802802080008000", + "0x4002801f80017fff", + "0x4802802080018000", "0x484480017fff8000", "0xffffffffffffffff", "0x48307fff7ffc8000", - "0x4002802380007fff", + "0x4002802380017fff", "0x480680017fff8000", "0xffffffffffffffff00000000000000", - "0x4002802480007fff", - "0x4802802580008000", + "0x4002802480017fff", + "0x4802802580018000", "0x484480017fff8000", "0xffffffffffffffffffffffffffffffff", "0x48307fff7ffc8000", - "0x484480017fff8000", - "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", "0x480680017fff8000", "0x100000000", - "0x480080007fd58005", - "0x480080017fd48005", + "0x4802800480008000", + "0x4826800180008000", + "0x8", + "0x484480017feb8000", + "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", + "0x4826800180018000", + "0x28", + "0x484480017ffa8000", + "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", + "0x20680017fff7ffa", + "0x11", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4469766973696f6e2062792030", + "0x400080007ffe7fff", + "0x48127fcf7fff8000", + "0x48127ff87fff8000", + "0x48127ffa7fff8000", + "0x48127ff77fff8000", + "0x480680017fff8000", + "0x1", + "0x48127ff97fff8000", + "0x482480017ff88000", + "0x1", + "0x208b7fff7fff7ffe", + "0x480080007fd18005", + "0x480080017fd08005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", - "0x480080027fd17ffd", + "0x48307ffd7ffe7ff7", + "0x480080027fcd7ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff47ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", "0xffffffffffffffff0000000000000000", - "0x400080037fce7ffc", - "0x40507ffe7ff87ffd", - "0x40307fff7ffd7ff7", + "0x400080037fca7ffc", + "0x40507ffe7ff37ffd", + "0x40307fff7ffd7ff8", "0x484480017fff8000", "0x100000000000000000000000000000000", - "0x484480017fe48000", - "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", - "0x482480017fcc8000", + "0x482480017fc98000", "0x4", - "0x4802800480018000", - "0x4826800180008000", - "0x28", - "0x4826800180018000", - "0x8", + "0x48127ff27fff8000", + "0x48127ff47fff8000", + "0x48127ff17fff8000", "0x480680017fff8000", "0x0", "0x480680017fff8000", "0x0", - "0x48307ff97ff88000", + "0x48307fef7ff98000", "0x208b7fff7fff7ffe", "0x48127ff97fff8000", - "0x4802800480018000", - "0x4826800180018000", + "0x4802800480008000", + "0x4826800180008000", "0x8", - "0x4802800680018000", - "0x4802800780018000", + "0x4802800680008000", + "0x4802800780008000", "0x10780017fff7fff", "0xe", "0x48127ffb7fff8000", "0x48127ffb7fff8000", - "0x480a80017fff8000", + "0x480a80007fff8000", "0x48127ffb7fff8000", "0x48127ffb7fff8000", "0x10780017fff7fff", "0x7", "0x48127ff87fff8000", "0x48127ff87fff8000", - "0x480a80017fff8000", + "0x480a80007fff8000", "0x48127ffb7fff8000", "0x48127ffb7fff8000", "0x48127ffb7fff8000", "0x48127ffb7fff8000", - "0x480a80007fff8000", + "0x480a80017fff8000", "0x48127ffa7fff8000", "0x480680017fff8000", "0x1", @@ -14596,15 +14755,15 @@ "0x48127f8d7fff8000", "0x208b7fff7fff7ffe", "0x1104800180018000", - "0x150", + "0x170", "0x482480017fff8000", - "0x14f", + "0x16f", "0x480080007fff8000", "0x480080017fff8000", "0x484480017fff8000", "0x8", "0x482480017fff8000", - "0x3b06", + "0x3c32", "0xa0680017fff8000", "0x8", "0x48317ffe80007ff8", @@ -14649,17 +14808,17 @@ "0x20680017fff7ffd", "0xc", "0x48127ffb7fff8000", - "0x48127fa87fff8000", + "0x48127fa77fff8000", "0x48127ffa7fff8000", - "0x48127fa97fff8000", - "0x48127fa97fff8000", + "0x48127fa87fff8000", + "0x48127fa87fff8000", "0x48127ff97fff8000", "0x48127ff97fff8000", "0x1104800180018000", "0x800000000000010ffffffffffffffffffffffffffffffffffffffffffffffc5", "0x208b7fff7fff7ffe", "0x48127ffb7fff8000", - "0x48127fa87fff8000", + "0x48127fa77fff8000", "0x48127ffa7fff8000", "0x480680017fff8000", "0x1", @@ -14731,140 +14890,172 @@ "0x484480017fff8000", "0xffffffffffffffffffffffffffffffff", "0x48307fff7ffc8000", - "0x484480017fff8000", - "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", "0x480680017fff8000", "0x10000000000000000", + "0x482680017ff98000", + "0x14", + "0x484480017ffd8000", + "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", + "0x20680017fff7ffd", + "0xf", + "0x40780017fff7fff", + "0x2c", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x480a7ff87fff8000", + "0x48127ffd7fff8000", + "0x482480017ffc8000", + "0x1", + "0x10780017fff7fff", + "0xce", "0x480280007ff88005", "0x480280017ff88005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", + "0x48307ffd7ffe7ffa", "0x480280027ff87ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff77ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", "0xffffffffffffffff0000000000000000", "0x400280037ff87ffc", - "0x40507ffe7ff87ffd", - "0x40307fff7ffd7ff7", - "0x482680017ff98000", - "0x14", + "0x40507ffe7ff67ffd", + "0x40307fff7ffd7ff8", "0xa0680017fff8000", "0x7", - "0x4824800180007ffc", + "0x4824800180007ffd", "0x10000000000000000", "0x400280047ff87fff", "0x10780017fff7fff", - "0x99", - "0x482480017ffc8000", + "0xaa", + "0x482480017ffd8000", "0xffffffffffffffff0000000000000000", "0x400280047ff87fff", "0xa0680017fff8000", "0x7", - "0x4824800180007ffb", + "0x4824800180007ffc", "0x10000000000000000", "0x400280057ff87fff", "0x10780017fff7fff", - "0x81", - "0x482480017ffb8000", + "0x92", + "0x482480017ffc8000", "0xffffffffffffffff0000000000000000", "0x400280057ff87fff", - "0x400280007ffb7ffa", - "0x400280017ffb7ff9", - "0x400180007ffb7ffc", + "0x400280007ffb7ffb", + "0x400280017ffb7ffa", + "0x400180007ff37ffc", "0x480680017fff8000", "0xff00ff00ff00ff00ff00ff00ff00ff", - "0x400080017ffa7fff", - "0x480080027ffa8000", + "0x400080017ff27fff", + "0x480080027ff28000", "0x484480017fff8000", "0xffff", "0x48327fff7ffc8000", - "0x400080057ff77fff", + "0x400080057fef7fff", "0x480680017fff8000", "0xffff0000ffff0000ffff0000ffff00", - "0x400080067ff67fff", - "0x480080077ff68000", + "0x400080067fee7fff", + "0x480080077fee8000", "0x484480017fff8000", "0xffffffff", "0x48307fff7ffc8000", - "0x4000800a7ff37fff", + "0x4000800a7feb7fff", "0x480680017fff8000", "0xffffffff00000000ffffffff000000", - "0x4000800b7ff27fff", - "0x4800800c7ff28000", + "0x4000800b7fea7fff", + "0x4800800c7fea8000", "0x484480017fff8000", "0xffffffffffffffff", "0x48307fff7ffc8000", - "0x4000800f7fef7fff", + "0x4000800f7fe77fff", "0x480680017fff8000", "0xffffffffffffffff00000000000000", - "0x400080107fee7fff", - "0x480080117fee8000", + "0x400080107fe67fff", + "0x480080117fe68000", "0x484480017fff8000", "0xffffffffffffffffffffffffffffffff", "0x48307fff7ffc8000", - "0x484480017fff8000", - "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", "0x480680017fff8000", "0x10000000000000000", - "0x480280067ff88005", - "0x480280077ff88005", + "0x482680017ff88000", + "0x6", + "0x480a7ffa7fff8000", + "0x482680017ffb8000", + "0x2", + "0x482480017fdf8000", + "0x14", + "0x484480017ffa8000", + "0x800000000000010fffffffffffffff7ffffffffffffef000000000000000001", + "0x20680017fff7ffa", + "0xf", + "0x40780017fff7fff", + "0xb", + "0x40780017fff7fff", + "0x1", + "0x480680017fff8000", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x400080007ffe7fff", + "0x48127fee7fff8000", + "0x48127ffd7fff8000", + "0x482480017ffc8000", + "0x1", + "0x10780017fff7fff", + "0x4c", + "0x480080007ffb8005", + "0x480080017ffa8005", "0x4824800180047ffe", "0x1", - "0x48307ffd7ffe7ffc", - "0x480280087ff87ffd", + "0x48307ffd7ffe7ff7", + "0x480080027ff77ffd", "0xa0680017fff7ffd", "0x6", - "0x482480017ff97ffd", + "0x482480017ff47ffd", "0xffffffffffffffff0000000000000000", "0x10780017fff7fff", "0x4", "0x482480017fff7ffd", - "0xffffffffffffffff0000000000000000", - "0x400280097ff87ffc", - "0x40507ffe7ff87ffd", - "0x40307fff7ffd7ff7", - "0x480a7ffa7fff8000", - "0x482680017ffb8000", - "0x2", - "0x482480017fe08000", - "0x14", + "0xffffffffffffffff0000000000000000", + "0x400080037ff47ffc", + "0x40507ffe7ff37ffd", + "0x40307fff7ffd7ff8", "0xa0680017fff8000", "0x7", - "0x4824800180007ffa", + "0x4824800180007ffd", "0x10000000000000000", - "0x4002800a7ff87fff", + "0x400080047ff27fff", "0x10780017fff7fff", "0x28", - "0x482480017ffa8000", + "0x482480017ffd8000", "0xffffffffffffffff0000000000000000", - "0x4002800a7ff87fff", + "0x400080047ff27fff", "0xa0680017fff8000", "0x7", - "0x4824800180007ff9", + "0x4824800180007ffc", "0x10000000000000000", - "0x4002800b7ff87fff", + "0x400080057ff07fff", "0x10780017fff7fff", "0x12", - "0x482480017ff98000", + "0x482480017ffc8000", "0xffffffffffffffff0000000000000000", - "0x4002800b7ff87fff", + "0x400080057ff07fff", "0x40780017fff7fff", "0x5", - "0x400080007ff57ff3", - "0x400080017ff57ff2", - "0x482680017ff88000", - "0xc", - "0x48127ff57fff8000", + "0x400080007fed7ff6", + "0x400080017fed7ff5", + "0x482480017feb8000", + "0x6", + "0x48127fed7fff8000", "0x480680017fff8000", "0x0", - "0x48127ff17fff8000", - "0x482480017ff18000", + "0x48127fe97fff8000", + "0x482480017fe98000", "0x2", "0x208b7fff7fff7ffe", "0x40780017fff7fff", @@ -14872,8 +15063,8 @@ "0x480680017fff8000", "0x4f7074696f6e3a3a756e77726170206661696c65642e", "0x400080007ffe7fff", - "0x482680017ff88000", - "0xc", + "0x482480017fee8000", + "0x6", "0x48127ffd7fff8000", "0x482480017ffc8000", "0x1", @@ -14886,20 +15077,20 @@ "0x480680017fff8000", "0x4f7074696f6e3a3a756e77726170206661696c65642e", "0x400080007ffe7fff", - "0x482680017ff88000", - "0xb", + "0x482480017fee8000", + "0x5", "0x48127ffd7fff8000", "0x482480017ffc8000", "0x1", "0x48127ffd7fff8000", - "0x48127ff57fff8000", + "0x48127fed7fff8000", "0x480680017fff8000", "0x1", "0x48127ffb7fff8000", "0x48127ffb7fff8000", "0x208b7fff7fff7ffe", "0x40780017fff7fff", - "0x20", + "0x21", "0x40780017fff7fff", "0x1", "0x480680017fff8000", @@ -14913,7 +15104,7 @@ "0x10780017fff7fff", "0xe", "0x40780017fff7fff", - "0x22", + "0x23", "0x40780017fff7fff", "0x1", "0x480680017fff8000", @@ -14925,7 +15116,7 @@ "0x482480017ffc8000", "0x1", "0x48127ffd7fff8000", - "0x48127fd57fff8000", + "0x48127fcc7fff8000", "0x480680017fff8000", "0x1", "0x48127ffb7fff8000", @@ -14971,9 +15162,9 @@ 72, 190, 212, - 352, - 393, - 335, + 369, + 411, + 360, 48, 44, 195, @@ -14986,17 +15177,17 @@ 129, 151, 89, - 306, + 321, 220, - 791, + 807, 176, - 157, + 189, 83, - 706, - 201, + 722, + 221, 552, 104, - 232 + 264 ], "hints": [ [ @@ -17472,7 +17663,7 @@ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x7b3e" + "Immediate": "0x7ba2" }, "rhs": { "Deref": { @@ -17667,7 +17858,7 @@ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x382d4" + "Immediate": "0x38400" }, "rhs": { "Deref": { @@ -21099,19 +21290,32 @@ ], [ 8525, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 8542, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -2 } }, "quotient": { @@ -21127,7 +21331,7 @@ ] ], [ - 8531, + 8548, [ { "TestLessThan": { @@ -21149,7 +21353,7 @@ ] ], [ - 8582, + 8599, [ { "AllocSegment": { @@ -21162,7 +21366,7 @@ ] ], [ - 8613, + 8630, [ { "AllocSegment": { @@ -21175,7 +21379,7 @@ ] ], [ - 8638, + 8655, [ { "AllocSegment": { @@ -21188,7 +21392,7 @@ ] ], [ - 8653, + 8670, [ { "AllocSegment": { @@ -21201,7 +21405,7 @@ ] ], [ - 8695, + 8712, [ { "SystemCall": { @@ -21216,7 +21420,7 @@ ] ], [ - 8707, + 8724, [ { "AllocSegment": { @@ -21229,7 +21433,7 @@ ] ], [ - 8737, + 8754, [ { "SystemCall": { @@ -21244,7 +21448,7 @@ ] ], [ - 8742, + 8759, [ { "AllocSegment": { @@ -21257,7 +21461,7 @@ ] ], [ - 8765, + 8782, [ { "TestLessThan": { @@ -21282,7 +21486,7 @@ ] ], [ - 8799, + 8816, [ { "SystemCall": { @@ -21297,7 +21501,7 @@ ] ], [ - 8814, + 8831, [ { "SystemCall": { @@ -21312,7 +21516,20 @@ ] ], [ - 8862, + 8879, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 8899, [ { "AllocSegment": { @@ -21325,20 +21542,20 @@ ] ], [ - 8880, + 8915, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -2 } }, "quotient": { @@ -21354,7 +21571,7 @@ ] ], [ - 8886, + 8921, [ { "TestLessThan": { @@ -21376,7 +21593,7 @@ ] ], [ - 8915, + 8950, [ { "SystemCall": { @@ -21391,7 +21608,7 @@ ] ], [ - 8965, + 9000, [ { "AllocSegment": { @@ -21404,7 +21621,7 @@ ] ], [ - 9003, + 9038, [ { "AllocSegment": { @@ -21417,7 +21634,7 @@ ] ], [ - 9030, + 9065, [ { "AllocSegment": { @@ -21430,7 +21647,7 @@ ] ], [ - 9046, + 9081, [ { "AllocSegment": { @@ -21443,7 +21660,7 @@ ] ], [ - 9072, + 9107, [ { "TestLessThanOrEqual": { @@ -21465,7 +21682,7 @@ ] ], [ - 9086, + 9121, [ { "TestLessThan": { @@ -21487,7 +21704,7 @@ ] ], [ - 9161, + 9196, [ { "TestLessThan": { @@ -21509,7 +21726,7 @@ ] ], [ - 9165, + 9200, [ { "LinearSplit": { @@ -21538,7 +21755,7 @@ ] ], [ - 9175, + 9210, [ { "LinearSplit": { @@ -21567,7 +21784,7 @@ ] ], [ - 9193, + 9228, [ { "SystemCall": { @@ -21582,7 +21799,7 @@ ] ], [ - 9211, + 9246, [ { "AllocSegment": { @@ -21595,7 +21812,7 @@ ] ], [ - 9230, + 9265, [ { "TestLessThan": { @@ -21617,7 +21834,7 @@ ] ], [ - 9234, + 9269, [ { "LinearSplit": { @@ -21646,7 +21863,7 @@ ] ], [ - 9244, + 9279, [ { "LinearSplit": { @@ -21675,7 +21892,7 @@ ] ], [ - 9262, + 9297, [ { "SystemCall": { @@ -21690,7 +21907,7 @@ ] ], [ - 9280, + 9315, [ { "AllocSegment": { @@ -21703,7 +21920,7 @@ ] ], [ - 9311, + 9346, [ { "AllocSegment": { @@ -21716,7 +21933,7 @@ ] ], [ - 9335, + 9370, [ { "AllocSegment": { @@ -21729,7 +21946,7 @@ ] ], [ - 9349, + 9384, [ { "AllocSegment": { @@ -21742,7 +21959,7 @@ ] ], [ - 9363, + 9398, [ { "AllocSegment": { @@ -21755,7 +21972,7 @@ ] ], [ - 9377, + 9418, [ { "AllocSegment": { @@ -21768,7 +21985,36 @@ ] ], [ - 9392, + 9434, + [ + { + "DivMod": { + "lhs": { + "Deref": { + "register": "AP", + "offset": -1 + } + }, + "rhs": { + "Deref": { + "register": "AP", + "offset": -3 + } + }, + "quotient": { + "register": "AP", + "offset": 3 + }, + "remainder": { + "register": "AP", + "offset": 4 + } + } + } + ] + ], + [ + 9452, [ { "AllocSegment": { @@ -21781,7 +22027,7 @@ ] ], [ - 9407, + 9467, [ { "TestLessThanOrEqual": { @@ -21803,7 +22049,7 @@ ] ], [ - 9421, + 9481, [ { "AllocSegment": { @@ -21816,7 +22062,7 @@ ] ], [ - 9441, + 9501, [ { "AllocSegment": { @@ -21829,7 +22075,7 @@ ] ], [ - 9455, + 9515, [ { "TestLessThanOrEqual": { @@ -21851,7 +22097,7 @@ ] ], [ - 9485, + 9545, [ { "AllocSegment": { @@ -21864,7 +22110,7 @@ ] ], [ - 9504, + 9564, [ { "TestLessThan": { @@ -21886,7 +22132,7 @@ ] ], [ - 9508, + 9568, [ { "LinearSplit": { @@ -21915,7 +22161,7 @@ ] ], [ - 9519, + 9579, [ { "LinearSplit": { @@ -21944,7 +22190,7 @@ ] ], [ - 9545, + 9605, [ { "SystemCall": { @@ -21959,7 +22205,7 @@ ] ], [ - 9560, + 9620, [ { "SystemCall": { @@ -21980,7 +22226,7 @@ ] ], [ - 9568, + 9628, [ { "TestLessThan": { @@ -22002,7 +22248,7 @@ ] ], [ - 9572, + 9632, [ { "LinearSplit": { @@ -22031,7 +22277,7 @@ ] ], [ - 9583, + 9643, [ { "LinearSplit": { @@ -22060,7 +22306,7 @@ ] ], [ - 9613, + 9673, [ { "SystemCall": { @@ -22081,7 +22327,7 @@ ] ], [ - 9629, + 9689, [ { "SystemCall": { @@ -22102,7 +22348,7 @@ ] ], [ - 9737, + 9797, [ { "TestLessThan": { @@ -22124,7 +22370,7 @@ ] ], [ - 9739, + 9799, [ { "DivMod": { @@ -22150,7 +22396,7 @@ ] ], [ - 9784, + 9844, [ { "TestLessThan": { @@ -22172,7 +22418,7 @@ ] ], [ - 9786, + 9846, [ { "DivMod": { @@ -22198,7 +22444,7 @@ ] ], [ - 9895, + 9955, [ { "TestLessThan": { @@ -22220,7 +22466,7 @@ ] ], [ - 9899, + 9959, [ { "LinearSplit": { @@ -22249,7 +22495,7 @@ ] ], [ - 9910, + 9970, [ { "LinearSplit": { @@ -22278,7 +22524,7 @@ ] ], [ - 9936, + 9996, [ { "SystemCall": { @@ -22293,7 +22539,7 @@ ] ], [ - 9951, + 10011, [ { "SystemCall": { @@ -22314,7 +22560,7 @@ ] ], [ - 9958, + 10018, [ { "TestLessThan": { @@ -22336,7 +22582,7 @@ ] ], [ - 9960, + 10020, [ { "DivMod": { @@ -22362,7 +22608,7 @@ ] ], [ - 9981, + 10041, [ { "TestLessThan": { @@ -22384,7 +22630,7 @@ ] ], [ - 9983, + 10043, [ { "DivMod": { @@ -22410,7 +22656,7 @@ ] ], [ - 10013, + 10073, [ { "TestLessThan": { @@ -22432,7 +22678,7 @@ ] ], [ - 10017, + 10077, [ { "LinearSplit": { @@ -22461,7 +22707,7 @@ ] ], [ - 10028, + 10088, [ { "LinearSplit": { @@ -22490,7 +22736,7 @@ ] ], [ - 10059, + 10119, [ { "SystemCall": { @@ -22505,7 +22751,7 @@ ] ], [ - 10074, + 10134, [ { "SystemCall": { @@ -22526,7 +22772,7 @@ ] ], [ - 10118, + 10178, [ { "AllocSegment": { @@ -22539,7 +22785,7 @@ ] ], [ - 10137, + 10197, [ { "AllocSegment": { @@ -22552,7 +22798,7 @@ ] ], [ - 10219, + 10279, [ { "RandomEcPoint": { @@ -22580,7 +22826,7 @@ ] ], [ - 10283, + 10343, [ { "RandomEcPoint": { @@ -22608,7 +22854,7 @@ ] ], [ - 10353, + 10413, [ { "AllocSegment": { @@ -22621,7 +22867,7 @@ ] ], [ - 10378, + 10438, [ { "SystemCall": { @@ -22636,7 +22882,7 @@ ] ], [ - 10395, + 10455, [ { "SystemCall": { @@ -22657,7 +22903,7 @@ ] ], [ - 10435, + 10495, [ { "AllocSegment": { @@ -22670,7 +22916,7 @@ ] ], [ - 10451, + 10511, [ { "AllocSegment": { @@ -22683,7 +22929,7 @@ ] ], [ - 10469, + 10529, [ { "SystemCall": { @@ -22698,7 +22944,7 @@ ] ], [ - 10479, + 10539, [ { "TestLessThan": { @@ -22720,7 +22966,7 @@ ] ], [ - 10483, + 10543, [ { "LinearSplit": { @@ -22749,7 +22995,7 @@ ] ], [ - 10494, + 10554, [ { "LinearSplit": { @@ -22778,7 +23024,7 @@ ] ], [ - 10538, + 10598, [ { "SystemCall": { @@ -22799,7 +23045,7 @@ ] ], [ - 10553, + 10613, [ { "SystemCall": { @@ -22820,7 +23066,7 @@ ] ], [ - 10563, + 10623, [ { "TestLessThan": { @@ -22845,7 +23091,7 @@ ] ], [ - 10578, + 10638, [ { "TestLessThan": { @@ -22870,7 +23116,7 @@ ] ], [ - 10594, + 10654, [ { "TestLessThan": { @@ -22892,7 +23138,7 @@ ] ], [ - 10598, + 10658, [ { "LinearSplit": { @@ -22921,7 +23167,7 @@ ] ], [ - 10609, + 10669, [ { "LinearSplit": { @@ -22950,7 +23196,7 @@ ] ], [ - 10638, + 10698, [ { "SystemCall": { @@ -22965,7 +23211,7 @@ ] ], [ - 10654, + 10714, [ { "SystemCall": { @@ -22986,7 +23232,7 @@ ] ], [ - 10696, + 10756, [ { "AllocSegment": { @@ -22999,7 +23245,7 @@ ] ], [ - 10714, + 10774, [ { "AllocSegment": { @@ -23012,7 +23258,7 @@ ] ], [ - 10789, + 10849, [ { "TestLessThan": { @@ -23040,7 +23286,7 @@ ] ], [ - 10793, + 10853, [ { "LinearSplit": { @@ -23069,7 +23315,7 @@ ] ], [ - 10815, + 10875, [ { "TestLessThanOrEqual": { @@ -23094,7 +23340,7 @@ ] ], [ - 10829, + 10889, [ { "TestLessThan": { @@ -23116,7 +23362,7 @@ ] ], [ - 10839, + 10899, [ { "TestLessThanOrEqual": { @@ -23141,7 +23387,7 @@ ] ], [ - 10862, + 10922, [ { "AllocSegment": { @@ -23154,7 +23400,7 @@ ] ], [ - 10883, + 10943, [ { "AllocSegment": { @@ -23167,7 +23413,7 @@ ] ], [ - 10904, + 10964, [ { "AllocSegment": { @@ -23180,7 +23426,7 @@ ] ], [ - 10952, + 11012, [ { "TestLessThanOrEqual": { @@ -23202,7 +23448,7 @@ ] ], [ - 11012, + 11072, [ { "AllocSegment": { @@ -23215,7 +23461,7 @@ ] ], [ - 11032, + 11092, [ { "TestLessThanOrEqual": { @@ -23237,7 +23483,7 @@ ] ], [ - 11111, + 11171, [ { "AllocSegment": { @@ -23250,7 +23496,7 @@ ] ], [ - 11141, + 11201, [ { "AllocSegment": { @@ -23263,7 +23509,7 @@ ] ], [ - 11161, + 11221, [ { "TestLessThanOrEqual": { @@ -23285,7 +23531,7 @@ ] ], [ - 11262, + 11322, [ { "AllocSegment": { @@ -23298,7 +23544,7 @@ ] ], [ - 11292, + 11352, [ { "AllocSegment": { @@ -23311,12 +23557,12 @@ ] ], [ - 11312, + 11372, [ { "TestLessThanOrEqual": { "lhs": { - "Immediate": "0x22ce" + "Immediate": "0x23fa" }, "rhs": { "Deref": { @@ -23333,7 +23579,7 @@ ] ], [ - 11383, + 11443, [ { "AllocSegment": { @@ -23346,20 +23592,33 @@ ] ], [ - 11404, + 11465, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 11479, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -2 } }, "quotient": { @@ -23375,7 +23634,7 @@ ] ], [ - 11462, + 11537, [ { "AllocSegment": { @@ -23388,7 +23647,7 @@ ] ], [ - 11515, + 11590, [ { "AllocSegment": { @@ -23401,7 +23660,7 @@ ] ], [ - 11528, + 11603, [ { "DivMod": { @@ -23430,7 +23689,7 @@ ] ], [ - 11536, + 11611, [ { "TestLessThan": { @@ -23461,7 +23720,7 @@ ] ], [ - 11553, + 11628, [ { "AllocSegment": { @@ -23474,7 +23733,7 @@ ] ], [ - 11577, + 11652, [ { "TestLessThan": { @@ -23496,7 +23755,7 @@ ] ], [ - 11601, + 11676, [ { "TestLessThan": { @@ -23518,7 +23777,7 @@ ] ], [ - 11610, + 11685, [ { "TestLessThan": { @@ -23540,7 +23799,7 @@ ] ], [ - 11627, + 11702, [ { "AllocSegment": { @@ -23553,7 +23812,7 @@ ] ], [ - 11641, + 11716, [ { "AllocSegment": { @@ -23566,7 +23825,7 @@ ] ], [ - 11657, + 11732, [ { "TestLessThan": { @@ -23597,7 +23856,7 @@ ] ], [ - 11679, + 11754, [ { "AllocSegment": { @@ -23610,7 +23869,7 @@ ] ], [ - 11693, + 11768, [ { "AllocSegment": { @@ -23623,7 +23882,7 @@ ] ], [ - 11724, + 11799, [ { "TestLessThan": { @@ -23645,7 +23904,7 @@ ] ], [ - 11746, + 11821, [ { "TestLessThan": { @@ -23667,7 +23926,7 @@ ] ], [ - 11783, + 11858, [ { "TestLessThan": { @@ -23689,7 +23948,7 @@ ] ], [ - 11805, + 11880, [ { "TestLessThan": { @@ -23711,7 +23970,7 @@ ] ], [ - 11881, + 11956, [ { "AllocSegment": { @@ -23724,7 +23983,7 @@ ] ], [ - 11946, + 12021, [ { "TestLessThan": { @@ -23746,7 +24005,7 @@ ] ], [ - 11970, + 12045, [ { "TestLessThan": { @@ -23768,7 +24027,7 @@ ] ], [ - 12011, + 12086, [ { "TestLessThan": { @@ -23790,7 +24049,7 @@ ] ], [ - 12037, + 12112, [ { "TestLessThan": { @@ -23812,7 +24071,20 @@ ] ], [ - 12081, + 12162, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 12176, [ { "U256InvModN": { @@ -23869,7 +24141,7 @@ ] ], [ - 12099, + 12194, [ { "WideMul128": { @@ -24066,7 +24338,7 @@ ] ], [ - 12152, + 12247, [ { "WideMul128": { @@ -24119,7 +24391,7 @@ ] ], [ - 12156, + 12251, [ { "TestLessThan": { @@ -24141,7 +24413,7 @@ ] ], [ - 12170, + 12265, [ { "TestLessThan": { @@ -24163,7 +24435,7 @@ ] ], [ - 12183, + 12278, [ { "DivMod": { @@ -24189,7 +24461,7 @@ ] ], [ - 12193, + 12288, [ { "DivMod": { @@ -24215,7 +24487,7 @@ ] ], [ - 12204, + 12299, [ { "DivMod": { @@ -24241,7 +24513,7 @@ ] ], [ - 12213, + 12308, [ { "DivMod": { @@ -24267,7 +24539,7 @@ ] ], [ - 12223, + 12318, [ { "DivMod": { @@ -24293,7 +24565,7 @@ ] ], [ - 12234, + 12329, [ { "DivMod": { @@ -24319,7 +24591,7 @@ ] ], [ - 12243, + 12338, [ { "DivMod": { @@ -24345,7 +24617,7 @@ ] ], [ - 12253, + 12348, [ { "DivMod": { @@ -24371,7 +24643,7 @@ ] ], [ - 12264, + 12359, [ { "DivMod": { @@ -24397,7 +24669,7 @@ ] ], [ - 12273, + 12368, [ { "DivMod": { @@ -24423,7 +24695,7 @@ ] ], [ - 12283, + 12378, [ { "DivMod": { @@ -24449,7 +24721,7 @@ ] ], [ - 12294, + 12389, [ { "DivMod": { @@ -24475,7 +24747,7 @@ ] ], [ - 12303, + 12398, [ { "DivMod": { @@ -24501,7 +24773,7 @@ ] ], [ - 12313, + 12408, [ { "DivMod": { @@ -24527,7 +24799,7 @@ ] ], [ - 12324, + 12419, [ { "DivMod": { @@ -24553,7 +24825,7 @@ ] ], [ - 12333, + 12428, [ { "DivMod": { @@ -24579,7 +24851,7 @@ ] ], [ - 12343, + 12438, [ { "DivMod": { @@ -24605,7 +24877,7 @@ ] ], [ - 12354, + 12449, [ { "DivMod": { @@ -24631,7 +24903,7 @@ ] ], [ - 12363, + 12458, [ { "DivMod": { @@ -24657,7 +24929,7 @@ ] ], [ - 12373, + 12468, [ { "DivMod": { @@ -24683,7 +24955,7 @@ ] ], [ - 12384, + 12479, [ { "DivMod": { @@ -24709,7 +24981,7 @@ ] ], [ - 12393, + 12488, [ { "DivMod": { @@ -24735,7 +25007,7 @@ ] ], [ - 12403, + 12498, [ { "DivMod": { @@ -24761,7 +25033,7 @@ ] ], [ - 12414, + 12509, [ { "DivMod": { @@ -24787,7 +25059,7 @@ ] ], [ - 12462, + 12553, [ { "SystemCall": { @@ -24802,7 +25074,7 @@ ] ], [ - 12479, + 12570, [ { "SystemCall": { @@ -24817,7 +25089,7 @@ ] ], [ - 12491, + 12582, [ { "SystemCall": { @@ -24838,7 +25110,7 @@ ] ], [ - 12502, + 12593, [ { "SystemCall": { @@ -24859,7 +25131,7 @@ ] ], [ - 12512, + 12603, [ { "SystemCall": { @@ -24880,7 +25152,7 @@ ] ], [ - 12597, + 12688, [ { "AllocSegment": { @@ -24893,7 +25165,7 @@ ] ], [ - 12626, + 12717, [ { "DivMod": { @@ -24919,7 +25191,7 @@ ] ], [ - 12636, + 12727, [ { "DivMod": { @@ -24945,7 +25217,7 @@ ] ], [ - 12647, + 12738, [ { "DivMod": { @@ -24971,7 +25243,7 @@ ] ], [ - 12656, + 12747, [ { "DivMod": { @@ -24997,7 +25269,7 @@ ] ], [ - 12666, + 12757, [ { "DivMod": { @@ -25023,7 +25295,7 @@ ] ], [ - 12677, + 12768, [ { "DivMod": { @@ -25049,7 +25321,7 @@ ] ], [ - 12686, + 12777, [ { "AllocSegment": { @@ -25062,7 +25334,7 @@ ] ], [ - 12761, + 12852, [ { "TestLessThan": { @@ -25090,7 +25362,7 @@ ] ], [ - 12765, + 12856, [ { "LinearSplit": { @@ -25119,7 +25391,7 @@ ] ], [ - 12807, + 12898, [ { "TestLessThan": { @@ -25141,7 +25413,7 @@ ] ], [ - 12809, + 12900, [ { "DivMod": { @@ -25167,7 +25439,20 @@ ] ], [ - 12896, + 12991, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 13002, [ { "DivMod": { @@ -25196,7 +25481,7 @@ ] ], [ - 12902, + 13008, [ { "TestLessThan": { @@ -25218,7 +25503,7 @@ ] ], [ - 12913, + 13019, [ { "TestLessThan": { @@ -25240,7 +25525,7 @@ ] ], [ - 12923, + 13029, [ { "TestLessThan": { @@ -25262,7 +25547,20 @@ ] ], [ - 12937, + 13052, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 13063, [ { "DivMod": { @@ -25275,7 +25573,7 @@ "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -4 } }, "quotient": { @@ -25291,7 +25589,7 @@ ] ], [ - 12943, + 13069, [ { "TestLessThan": { @@ -25313,14 +25611,14 @@ ] ], [ - 12957, + 13080, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -4 + "offset": -2 } }, "rhs": { @@ -25335,14 +25633,14 @@ ] ], [ - 12967, + 13090, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -5 + "offset": -3 } }, "rhs": { @@ -25357,7 +25655,7 @@ ] ], [ - 12989, + 13112, [ { "AllocSegment": { @@ -25370,7 +25668,7 @@ ] ], [ - 13003, + 13126, [ { "AllocSegment": { @@ -25383,7 +25681,7 @@ ] ], [ - 13021, + 13144, [ { "AllocSegment": { @@ -25396,7 +25694,7 @@ ] ], [ - 13035, + 13158, [ { "AllocSegment": { @@ -25409,7 +25707,7 @@ ] ], [ - 13051, + 13174, [ { "TestLessThanOrEqual": { @@ -25431,7 +25729,7 @@ ] ], [ - 13078, + 13201, [ { "TestLessThan": { @@ -25453,7 +25751,7 @@ ] ], [ - 13095, + 13218, [ { "AllocSegment": { @@ -25466,7 +25764,7 @@ ] ], [ - 13120, + 13243, [ { "AllocSegment": { @@ -25479,7 +25777,7 @@ ] ], [ - 13141, + 13264, [ { "SystemCall": { @@ -25494,7 +25792,7 @@ ] ], [ - 13167, + 13290, [ { "SystemCall": { @@ -25509,7 +25807,20 @@ ] ], [ - 13181, + 13310, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 13324, [ { "U256InvModN": { @@ -25566,7 +25877,7 @@ ] ], [ - 13199, + 13342, [ { "WideMul128": { @@ -25763,7 +26074,7 @@ ] ], [ - 13252, + 13395, [ { "WideMul128": { @@ -25816,7 +26127,7 @@ ] ], [ - 13256, + 13399, [ { "TestLessThan": { @@ -25838,7 +26149,7 @@ ] ], [ - 13270, + 13413, [ { "TestLessThan": { @@ -25860,7 +26171,7 @@ ] ], [ - 13283, + 13426, [ { "DivMod": { @@ -25886,7 +26197,7 @@ ] ], [ - 13293, + 13436, [ { "DivMod": { @@ -25912,7 +26223,7 @@ ] ], [ - 13304, + 13447, [ { "DivMod": { @@ -25938,7 +26249,7 @@ ] ], [ - 13313, + 13456, [ { "DivMod": { @@ -25964,7 +26275,7 @@ ] ], [ - 13323, + 13466, [ { "DivMod": { @@ -25990,7 +26301,7 @@ ] ], [ - 13334, + 13477, [ { "DivMod": { @@ -26016,7 +26327,7 @@ ] ], [ - 13343, + 13486, [ { "DivMod": { @@ -26042,7 +26353,7 @@ ] ], [ - 13353, + 13496, [ { "DivMod": { @@ -26068,7 +26379,7 @@ ] ], [ - 13364, + 13507, [ { "DivMod": { @@ -26094,7 +26405,7 @@ ] ], [ - 13373, + 13516, [ { "DivMod": { @@ -26120,7 +26431,7 @@ ] ], [ - 13383, + 13526, [ { "DivMod": { @@ -26146,7 +26457,7 @@ ] ], [ - 13394, + 13537, [ { "DivMod": { @@ -26172,7 +26483,7 @@ ] ], [ - 13403, + 13546, [ { "DivMod": { @@ -26198,7 +26509,7 @@ ] ], [ - 13413, + 13556, [ { "DivMod": { @@ -26224,7 +26535,7 @@ ] ], [ - 13424, + 13567, [ { "DivMod": { @@ -26250,7 +26561,7 @@ ] ], [ - 13433, + 13576, [ { "DivMod": { @@ -26276,7 +26587,7 @@ ] ], [ - 13443, + 13586, [ { "DivMod": { @@ -26302,7 +26613,7 @@ ] ], [ - 13454, + 13597, [ { "DivMod": { @@ -26328,7 +26639,7 @@ ] ], [ - 13463, + 13606, [ { "DivMod": { @@ -26354,7 +26665,7 @@ ] ], [ - 13473, + 13616, [ { "DivMod": { @@ -26380,7 +26691,7 @@ ] ], [ - 13484, + 13627, [ { "DivMod": { @@ -26406,7 +26717,7 @@ ] ], [ - 13493, + 13636, [ { "DivMod": { @@ -26432,7 +26743,7 @@ ] ], [ - 13503, + 13646, [ { "DivMod": { @@ -26458,7 +26769,7 @@ ] ], [ - 13514, + 13657, [ { "DivMod": { @@ -26484,7 +26795,7 @@ ] ], [ - 13538, + 13679, [ { "TestLessThan": { @@ -26506,7 +26817,7 @@ ] ], [ - 13563, + 13704, [ { "TestLessThan": { @@ -26528,7 +26839,7 @@ ] ], [ - 13583, + 13724, [ { "TestLessThan": { @@ -26550,7 +26861,7 @@ ] ], [ - 13628, + 13767, [ { "SystemCall": { @@ -26565,7 +26876,7 @@ ] ], [ - 13640, + 13779, [ { "SystemCall": { @@ -26586,7 +26897,7 @@ ] ], [ - 13651, + 13790, [ { "SystemCall": { @@ -26607,7 +26918,7 @@ ] ], [ - 13697, + 13836, [ { "AllocSegment": { @@ -26620,7 +26931,7 @@ ] ], [ - 13713, + 13852, [ { "DivMod": { @@ -26646,7 +26957,7 @@ ] ], [ - 13723, + 13862, [ { "DivMod": { @@ -26672,7 +26983,7 @@ ] ], [ - 13734, + 13873, [ { "DivMod": { @@ -26698,7 +27009,7 @@ ] ], [ - 13743, + 13882, [ { "DivMod": { @@ -26724,7 +27035,7 @@ ] ], [ - 13753, + 13892, [ { "DivMod": { @@ -26750,7 +27061,7 @@ ] ], [ - 13764, + 13903, [ { "DivMod": { @@ -26776,7 +27087,7 @@ ] ], [ - 13773, + 13912, [ { "AllocSegment": { @@ -26789,7 +27100,7 @@ ] ], [ - 13790, + 13929, [ { "AllocSegment": { @@ -26802,7 +27113,7 @@ ] ], [ - 13847, + 13986, [ { "SystemCall": { @@ -26817,7 +27128,7 @@ ] ], [ - 13850, + 13989, [ { "AllocSegment": { @@ -26830,7 +27141,7 @@ ] ], [ - 13860, + 13999, [ { "AllocSegment": { @@ -26843,14 +27154,14 @@ ] ], [ - 13895, + 14034, [ { "SystemCall": { "system": { "Deref": { "register": "FP", - "offset": 1 + "offset": 0 } } } @@ -26858,20 +27169,33 @@ ] ], [ - 13968, + 14116, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 14131, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -6 } }, "quotient": { @@ -26887,7 +27211,7 @@ ] ], [ - 13974, + 14137, [ { "TestLessThan": { @@ -26909,7 +27233,7 @@ ] ], [ - 14041, + 14200, [ { "WideMul128": { @@ -26938,7 +27262,7 @@ ] ], [ - 14043, + 14202, [ { "DivMod": { @@ -26964,7 +27288,7 @@ ] ], [ - 14053, + 14212, [ { "DivMod": { @@ -26990,7 +27314,7 @@ ] ], [ - 14064, + 14223, [ { "DivMod": { @@ -27016,7 +27340,7 @@ ] ], [ - 14073, + 14232, [ { "WideMul128": { @@ -27045,7 +27369,7 @@ ] ], [ - 14075, + 14234, [ { "DivMod": { @@ -27071,7 +27395,7 @@ ] ], [ - 14085, + 14244, [ { "DivMod": { @@ -27097,7 +27421,7 @@ ] ], [ - 14096, + 14255, [ { "DivMod": { @@ -27123,7 +27447,7 @@ ] ], [ - 14106, + 14265, [ { "TestLessThan": { @@ -27145,7 +27469,7 @@ ] ], [ - 14128, + 14287, [ { "WideMul128": { @@ -27174,7 +27498,7 @@ ] ], [ - 14130, + 14289, [ { "DivMod": { @@ -27200,7 +27524,7 @@ ] ], [ - 14140, + 14299, [ { "DivMod": { @@ -27226,7 +27550,7 @@ ] ], [ - 14151, + 14310, [ { "DivMod": { @@ -27252,7 +27576,7 @@ ] ], [ - 14161, + 14320, [ { "TestLessThan": { @@ -27274,7 +27598,7 @@ ] ], [ - 14184, + 14343, [ { "TestLessThan": { @@ -27296,7 +27620,7 @@ ] ], [ - 14206, + 14365, [ { "WideMul128": { @@ -27325,7 +27649,7 @@ ] ], [ - 14208, + 14367, [ { "DivMod": { @@ -27351,7 +27675,7 @@ ] ], [ - 14218, + 14377, [ { "DivMod": { @@ -27377,7 +27701,7 @@ ] ], [ - 14229, + 14388, [ { "DivMod": { @@ -27403,7 +27727,7 @@ ] ], [ - 14239, + 14398, [ { "TestLessThan": { @@ -27425,7 +27749,7 @@ ] ], [ - 14258, + 14417, [ { "TestLessThan": { @@ -27447,7 +27771,7 @@ ] ], [ - 14281, + 14440, [ { "TestLessThan": { @@ -27469,7 +27793,7 @@ ] ], [ - 14300, + 14459, [ { "TestLessThan": { @@ -27491,7 +27815,7 @@ ] ], [ - 14319, + 14478, [ { "TestLessThan": { @@ -27513,7 +27837,7 @@ ] ], [ - 14342, + 14501, [ { "TestLessThan": { @@ -27535,7 +27859,7 @@ ] ], [ - 14364, + 14523, [ { "Uint512DivModByUint256": { @@ -27604,7 +27928,7 @@ ] ], [ - 14382, + 14541, [ { "WideMul128": { @@ -27729,7 +28053,7 @@ ] ], [ - 14411, + 14570, [ { "TestLessThan": { @@ -27754,7 +28078,7 @@ ] ], [ - 14423, + 14582, [ { "TestLessThan": { @@ -27779,7 +28103,7 @@ ] ], [ - 14438, + 14597, [ { "DivMod": { @@ -27805,7 +28129,7 @@ ] ], [ - 14448, + 14607, [ { "DivMod": { @@ -27831,7 +28155,7 @@ ] ], [ - 14459, + 14618, [ { "DivMod": { @@ -27857,7 +28181,7 @@ ] ], [ - 14468, + 14627, [ { "DivMod": { @@ -27883,7 +28207,7 @@ ] ], [ - 14478, + 14637, [ { "DivMod": { @@ -27909,7 +28233,7 @@ ] ], [ - 14489, + 14648, [ { "DivMod": { @@ -27935,7 +28259,7 @@ ] ], [ - 14498, + 14657, [ { "DivMod": { @@ -27961,7 +28285,7 @@ ] ], [ - 14508, + 14667, [ { "DivMod": { @@ -27987,7 +28311,7 @@ ] ], [ - 14519, + 14678, [ { "DivMod": { @@ -28013,7 +28337,7 @@ ] ], [ - 14528, + 14687, [ { "DivMod": { @@ -28039,7 +28363,7 @@ ] ], [ - 14538, + 14697, [ { "DivMod": { @@ -28065,7 +28389,7 @@ ] ], [ - 14549, + 14708, [ { "DivMod": { @@ -28091,7 +28415,7 @@ ] ], [ - 14558, + 14717, [ { "DivMod": { @@ -28117,7 +28441,7 @@ ] ], [ - 14568, + 14727, [ { "DivMod": { @@ -28143,7 +28467,7 @@ ] ], [ - 14579, + 14738, [ { "DivMod": { @@ -28169,7 +28493,7 @@ ] ], [ - 14603, + 14762, [ { "TestLessThanOrEqual": { @@ -28194,7 +28518,20 @@ ] ], [ - 14678, + 14837, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 14898, [ { "AllocSegment": { @@ -28207,20 +28544,20 @@ ] ], [ - 14733, + 14909, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -3 } }, "quotient": { @@ -28236,7 +28573,7 @@ ] ], [ - 14739, + 14915, [ { "TestLessThan": { @@ -28258,14 +28595,14 @@ ] ], [ - 14752, + 14926, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -3 + "offset": -2 } }, "rhs": { @@ -28280,14 +28617,14 @@ ] ], [ - 14762, + 14936, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -4 + "offset": -3 } }, "rhs": { @@ -28302,20 +28639,33 @@ ] ], [ - 14810, + 14995, + [ + { + "AllocSegment": { + "dst": { + "register": "AP", + "offset": 0 + } + } + } + ] + ], + [ + 15006, [ { "DivMod": { "lhs": { "Deref": { "register": "AP", - "offset": -2 + "offset": -1 } }, "rhs": { "Deref": { "register": "AP", - "offset": -1 + "offset": -6 } }, "quotient": { @@ -28331,7 +28681,7 @@ ] ], [ - 14816, + 15012, [ { "TestLessThan": { @@ -28353,14 +28703,14 @@ ] ], [ - 14832, + 15023, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -5 + "offset": -2 } }, "rhs": { @@ -28375,14 +28725,14 @@ ] ], [ - 14842, + 15033, [ { "TestLessThan": { "lhs": { "Deref": { "register": "AP", - "offset": -6 + "offset": -3 } }, "rhs": { @@ -28397,7 +28747,7 @@ ] ], [ - 14865, + 15056, [ { "AllocSegment": { @@ -28410,7 +28760,7 @@ ] ], [ - 14879, + 15070, [ { "AllocSegment": { @@ -28423,7 +28773,7 @@ ] ], [ - 14898, + 15089, [ { "AllocSegment": { @@ -28436,7 +28786,7 @@ ] ], [ - 14912, + 15103, [ { "AllocSegment": { diff --git a/crates/blockifier/src/blockifier.rs b/crates/blockifier/src/blockifier.rs index e6760888f05..6bd74a63ec6 100644 --- a/crates/blockifier/src/blockifier.rs +++ b/crates/blockifier/src/blockifier.rs @@ -1,3 +1,4 @@ pub mod block; +pub mod config; pub mod stateful_validator; pub mod transaction_executor; diff --git a/crates/blockifier/src/blockifier/block.rs b/crates/blockifier/src/blockifier/block.rs index c7bac7fb221..98ab8bf05b0 100644 --- a/crates/blockifier/src/blockifier/block.rs +++ b/crates/blockifier/src/blockifier/block.rs @@ -62,6 +62,7 @@ pub fn pre_process_block( block_info: BlockInfo, chain_info: ChainInfo, versioned_constants: VersionedConstants, + concurrency_mode: bool, ) -> StateResult { let should_block_hash_be_provided = block_info.block_number >= BlockNumber(constants::STORED_BLOCK_HASH_BUFFER); @@ -80,7 +81,7 @@ pub fn pre_process_block( return Err(StateError::OldBlockHashNotProvided); } - Ok(BlockContext { block_info, chain_info, versioned_constants, concurrency_mode: false }) + Ok(BlockContext { block_info, chain_info, versioned_constants, concurrency_mode }) } pub struct BlockNumberHashPair { diff --git a/crates/blockifier/src/blockifier/block_test.rs b/crates/blockifier/src/blockifier/block_test.rs index 7a48565af59..bf93491f22b 100644 --- a/crates/blockifier/src/blockifier/block_test.rs +++ b/crates/blockifier/src/blockifier/block_test.rs @@ -29,6 +29,7 @@ fn test_pre_process_block() { block_info, ChainInfo::default(), VersionedConstants::default(), + false, ) .unwrap(); @@ -48,7 +49,8 @@ fn test_pre_process_block() { None, block_info, ChainInfo::default(), - VersionedConstants::default() + VersionedConstants::default(), + false, ) .is_ok() ); @@ -61,6 +63,7 @@ fn test_pre_process_block() { block_info, ChainInfo::default(), VersionedConstants::default(), + false, ); assert_eq!( format!( diff --git a/crates/blockifier/src/blockifier/config.rs b/crates/blockifier/src/blockifier/config.rs new file mode 100644 index 00000000000..36e1d74a6f9 --- /dev/null +++ b/crates/blockifier/src/blockifier/config.rs @@ -0,0 +1,11 @@ +#[derive(Debug, Default, Clone)] +pub struct TransactionExecutorConfig { + pub concurrency_config: ConcurrencyConfig, +} + +#[derive(Debug, Default, Clone)] +pub struct ConcurrencyConfig { + pub enabled: bool, + pub n_workers: usize, + pub chunk_size: usize, +} diff --git a/crates/blockifier/src/blockifier/stateful_validator.rs b/crates/blockifier/src/blockifier/stateful_validator.rs index 69f190b7140..d551ed29d06 100644 --- a/crates/blockifier/src/blockifier/stateful_validator.rs +++ b/crates/blockifier/src/blockifier/stateful_validator.rs @@ -3,6 +3,7 @@ use starknet_api::hash::StarkFelt; use starknet_api::transaction::TransactionHash; use thiserror::Error; +use crate::blockifier::config::TransactionExecutorConfig; use crate::blockifier::transaction_executor::{TransactionExecutor, TransactionExecutorError}; use crate::bouncer::BouncerConfig; use crate::context::{BlockContext, TransactionContext}; @@ -48,7 +49,12 @@ impl StatefulValidator { max_nonce_for_validation_skip: Nonce, bouncer_config: BouncerConfig, ) -> Self { - let tx_executor = TransactionExecutor::new(state, block_context, bouncer_config); + let tx_executor = TransactionExecutor::new( + state, + block_context, + bouncer_config, + TransactionExecutorConfig::default(), + ); Self { tx_executor, max_nonce_for_validation_skip } } diff --git a/crates/blockifier/src/blockifier/transaction_executor.rs b/crates/blockifier/src/blockifier/transaction_executor.rs index f1c50d22b33..3c7dbe9a931 100644 --- a/crates/blockifier/src/blockifier/transaction_executor.rs +++ b/crates/blockifier/src/blockifier/transaction_executor.rs @@ -1,14 +1,17 @@ use std::sync::Arc; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use itertools::FoldWhile::{Continue, Done}; +use itertools::Itertools; use starknet_api::core::ClassHash; use thiserror::Error; -use crate::bouncer::{Bouncer, BouncerConfig}; +use crate::blockifier::config::TransactionExecutorConfig; +use crate::bouncer::{Bouncer, BouncerConfig, BouncerWeights}; use crate::context::BlockContext; use crate::execution::call_info::CallInfo; use crate::fee::actual_cost::TransactionReceipt; -use crate::state::cached_state::{CachedState, CommitmentStateDiff}; +use crate::state::cached_state::{CachedState, CommitmentStateDiff, TransactionalState}; use crate::state::errors::StateError; use crate::state::state_api::{State, StateReader}; use crate::transaction::account_transaction::AccountTransaction; @@ -38,6 +41,8 @@ pub type VisitedSegmentsMapping = Vec<(ClassHash, Vec)>; pub struct TransactionExecutor { pub block_context: BlockContext, pub bouncer: Bouncer, + // Note: this config must not affect the execution result (e.g. state diff and traces). + pub config: TransactionExecutorConfig, // State-related fields. pub state: CachedState, @@ -48,11 +53,13 @@ impl TransactionExecutor { state: CachedState, block_context: BlockContext, bouncer_config: BouncerConfig, + config: TransactionExecutorConfig, ) -> Self { log::debug!("Initializing Transaction Executor..."); // Note: the state might not be empty even at this point; it is the creator's // responsibility to tune the bouncer according to pre and post block process. - let tx_executor = Self { block_context, bouncer: Bouncer::new(bouncer_config), state }; + let tx_executor = + Self { block_context, bouncer: Bouncer::new(bouncer_config), config, state }; log::debug!("Initialized Transaction Executor."); tx_executor @@ -66,15 +73,18 @@ impl TransactionExecutor { tx: &Transaction, charge_fee: bool, ) -> TransactionExecutorResult { - let mut transactional_state = CachedState::create_transactional(&mut self.state); + let mut transactional_state = TransactionalState::create_transactional(&mut self.state); let validate = true; let tx_execution_result = tx.execute_raw(&mut transactional_state, &self.block_context, charge_fee, validate); match tx_execution_result { Ok(tx_execution_info) => { + let tx_state_changes_keys = + transactional_state.get_actual_state_changes()?.into_keys(); self.bouncer.try_update( - &mut transactional_state, + &transactional_state, + &tx_state_changes_keys, &tx_execution_info.summarize(), &tx_execution_info.actual_resources, )?; @@ -91,7 +101,39 @@ impl TransactionExecutor { /// Executes the given transactions on the state maintained by the executor. /// Stops if and when there is no more room in the block, and returns the executed transactions' /// results. + pub fn execute_txs( + &mut self, + txs: &[Transaction], + charge_fee: bool, + ) -> Vec> { + if !self.config.concurrency_config.enabled { + self.execute_txs_sequentially(txs, charge_fee) + } else { + txs.chunks(self.config.concurrency_config.chunk_size) + .fold_while(Vec::new(), |mut results, chunk| { + let chunk_results = self.execute_chunk(chunk, charge_fee); + if chunk_results.len() < chunk.len() { + // Block is full. + results.extend(chunk_results); + Done(results) + } else { + results.extend(chunk_results); + Continue(results) + } + }) + .into_inner() + } + } + pub fn execute_chunk( + &mut self, + _chunk: &[Transaction], + _charge_fee: bool, + ) -> Vec> { + todo!() + } + + pub fn execute_txs_sequentially( &mut self, txs: &[Transaction], charge_fee: bool, @@ -144,11 +186,12 @@ impl TransactionExecutor { Ok((validate_call_info, tx_receipt)) } - /// Returns the state diff and a list of contract class hash with the corresponding list of - /// visited segment values. + /// Returns the state diff, a list of contract class hash with the corresponding list of + /// visited segment values and the block weights. pub fn finalize( &mut self, - ) -> TransactionExecutorResult<(CommitmentStateDiff, VisitedSegmentsMapping)> { + ) -> TransactionExecutorResult<(CommitmentStateDiff, VisitedSegmentsMapping, BouncerWeights)> + { // Get the visited segments of each contract class. // This is done by taking all the visited PCs of each contract, and compress them to one // representative for each visited segment. @@ -162,6 +205,11 @@ impl TransactionExecutor { }) .collect::>()?; - Ok((self.state.to_state_diff(), visited_segments)) + log::debug!("Final block weights: {:?}.", self.bouncer.get_accumulated_weights()); + Ok(( + self.state.to_state_diff()?.into(), + visited_segments, + *self.bouncer.get_accumulated_weights(), + )) } } diff --git a/crates/blockifier/src/blockifier/transaction_executor_test.rs b/crates/blockifier/src/blockifier/transaction_executor_test.rs index f74e0b4b5fb..042c020db47 100644 --- a/crates/blockifier/src/blockifier/transaction_executor_test.rs +++ b/crates/blockifier/src/blockifier/transaction_executor_test.rs @@ -5,6 +5,7 @@ use starknet_api::hash::StarkFelt; use starknet_api::stark_felt; use starknet_api::transaction::{Fee, TransactionVersion}; +use crate::blockifier::config::TransactionExecutorConfig; use crate::blockifier::transaction_executor::{TransactionExecutor, TransactionExecutorError}; use crate::bouncer::{Bouncer, BouncerConfig, BouncerWeights}; use crate::context::BlockContext; @@ -43,8 +44,12 @@ fn tx_executor_test_body( charge_fee: bool, expected_bouncer_weights: BouncerWeights, ) { - let mut tx_executor = - TransactionExecutor::new(state, block_context, BouncerConfig::create_for_testing()); + let mut tx_executor = TransactionExecutor::new( + state, + block_context, + BouncerConfig::create_for_testing(), + TransactionExecutorConfig::default(), + ); // TODO(Arni, 30/03/2024): Consider adding a test for the transaction execution info. If A test // should not be added, rename the test to `test_bouncer_info`. // TODO(Arni, 30/03/2024): Test all bouncer weights. @@ -115,6 +120,7 @@ fn test_declare( declare_tx_args! { sender_address: account_contract.get_instance_address(0), class_hash: declared_contract.get_class_hash(), + compiled_class_hash: declared_contract.get_compiled_class_hash(), version: transaction_version, resource_bounds: l1_resource_bounds(0, DEFAULT_STRK_L1_GAS_PRICE), }, @@ -267,6 +273,7 @@ fn test_bouncing( }, ..BouncerConfig::default() }, + TransactionExecutorConfig::default(), ); tx_executor.bouncer.set_accumulated_weights(initial_bouncer_weights); @@ -285,7 +292,7 @@ fn test_bouncing( } #[rstest] -fn test_execute_chunk_bouncing(block_context: BlockContext) { +fn test_execute_txs_bouncing(block_context: BlockContext) { let TestInitData { state, account_address, contract_address, .. } = create_test_init_data(&block_context.chain_info, CairoVersion::Cairo1); @@ -297,7 +304,12 @@ fn test_execute_chunk_bouncing(block_context: BlockContext) { }, ..BouncerConfig::default() }; - let mut tx_executor = TransactionExecutor::new(state, block_context, bouncer_config.clone()); + let mut tx_executor = TransactionExecutor::new( + state, + block_context, + bouncer_config.clone(), + TransactionExecutorConfig::default(), + ); let txs: Vec = [ emit_n_events_tx(1, account_address, contract_address, nonce!(0_u32)), @@ -319,7 +331,7 @@ fn test_execute_chunk_bouncing(block_context: BlockContext) { .collect(); // Run. - let results = tx_executor.execute_chunk(&txs, true); + let results = tx_executor.execute_txs(&txs, true); // Check execution results. let expected_offset = 3; @@ -339,12 +351,12 @@ fn test_execute_chunk_bouncing(block_context: BlockContext) { // Check idempotency: excess transactions should not be added. let remaining_txs = &txs[expected_offset..]; - let remaining_tx_results = tx_executor.execute_chunk(remaining_txs, true); + let remaining_tx_results = tx_executor.execute_txs(remaining_txs, true); assert_eq!(remaining_tx_results.len(), 0); // Reset the bouncer and add the remaining transactions. tx_executor.bouncer = Bouncer::new(bouncer_config); - let remaining_tx_results = tx_executor.execute_chunk(remaining_txs, true); + let remaining_tx_results = tx_executor.execute_txs(remaining_txs, true); assert_eq!(remaining_tx_results.len(), 2); assert!(remaining_tx_results[0].is_ok()); diff --git a/crates/blockifier/src/bouncer.rs b/crates/blockifier/src/bouncer.rs index 028535967e2..e3fdddf77ed 100644 --- a/crates/blockifier/src/bouncer.rs +++ b/crates/blockifier/src/bouncer.rs @@ -3,7 +3,7 @@ use std::collections::{HashMap, HashSet}; use cairo_vm::serde::deserialize_program::BuiltinName; use cairo_vm::vm::runners::builtin_runner::HASH_BUILTIN_NAME; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use starknet_api::core::ClassHash; use crate::blockifier::transaction_executor::{ @@ -12,7 +12,7 @@ use crate::blockifier::transaction_executor::{ }; use crate::execution::call_info::ExecutionSummary; use crate::fee::gas_usage::get_onchain_data_segment_length; -use crate::state::cached_state::{StateChangesKeys, StorageEntry, TransactionalState}; +use crate::state::cached_state::{StateChangesKeys, StorageEntry}; use crate::state::state_api::StateReader; use crate::transaction::errors::TransactionExecutionError; use crate::transaction::objects::{ExecutionResourcesTraits, TransactionResources}; @@ -37,7 +37,7 @@ macro_rules! impl_checked_sub { pub type HashMapWrapper = HashMap; -#[derive(Debug, Default, PartialEq, Clone)] +#[derive(Clone, Debug, Default, PartialEq)] pub struct BouncerConfig { pub block_max_capacity: BouncerWeights, pub block_max_capacity_with_keccak: BouncerWeights, @@ -62,6 +62,7 @@ impl BouncerConfig { derive_more::Sub, Deserialize, PartialEq, + Serialize, )] /// Represents the execution resources counted throughout block creation. pub struct BouncerWeights { @@ -109,6 +110,7 @@ impl BouncerWeights { derive_more::Sub, Deserialize, PartialEq, + Serialize, )] pub struct BuiltinCount { pub bitwise: usize, @@ -200,15 +202,20 @@ impl Bouncer { /// Updates the bouncer with a new transaction. pub fn try_update( &mut self, - state: &mut TransactionalState<'_, S>, + state_reader: &S, + tx_state_changes_keys: &StateChangesKeys, tx_execution_summary: &ExecutionSummary, tx_resources: &TransactionResources, ) -> TransactionExecutorResult<()> { // The countings here should be linear in the transactional state changes and execution info // rather than the cumulative state attributes. - let state_changes_keys = self.get_state_changes_keys(state)?; - let tx_weights = - self.get_tx_weights(state, tx_execution_summary, tx_resources, &state_changes_keys)?; + let state_changes_keys = tx_state_changes_keys.difference(&self.state_changes_keys); + let tx_weights = self.get_tx_weights( + state_reader, + tx_execution_summary, + tx_resources, + &state_changes_keys, + )?; let mut max_capacity = self.bouncer_config.block_max_capacity; if self.accumulated_weights.builtin_count.keccak > 0 || tx_weights.builtin_count.keccak > 0 @@ -223,6 +230,11 @@ impl Bouncer { // Check if the transaction can fit the current block available capacity. if !max_capacity.has_room(self.accumulated_weights + tx_weights) { + log::debug!( + "Transaction cannot be added to the current block, block capacity reached; \ + transaction weights: {tx_weights:?}, block weights: {:?}.", + self.accumulated_weights + ); Err(TransactionExecutorError::BlockFull)? } @@ -233,7 +245,7 @@ impl Bouncer { pub fn get_tx_weights( &mut self, - state: &mut TransactionalState<'_, S>, + state_reader: &S, tx_execution_summary: &ExecutionSummary, tx_resources: &TransactionResources, state_changes_keys: &StateChangesKeys, @@ -242,7 +254,7 @@ impl Bouncer { tx_resources.starknet_resources.calculate_message_l1_resources(); let mut additional_os_resources = get_casm_hash_calculation_resources( - state, + state_reader, &self.executed_class_hashes, &tx_execution_summary.executed_class_hashes, )?; @@ -263,14 +275,6 @@ impl Bouncer { }) } - pub fn get_state_changes_keys( - &self, - state: &mut TransactionalState<'_, S>, - ) -> TransactionExecutorResult { - let tx_state_changes_keys = state.get_actual_state_changes()?.into_keys(); - Ok(tx_state_changes_keys.difference(&self.state_changes_keys)) - } - #[cfg(test)] pub fn set_accumulated_weights(&mut self, weights: BouncerWeights) { self.accumulated_weights = weights; @@ -280,7 +284,7 @@ impl Bouncer { /// Returns the estimated VM resources for Casm hash calculation (done by the OS), of the newly /// executed classes by the current transaction. pub fn get_casm_hash_calculation_resources( - state: &mut TransactionalState<'_, S>, + state_reader: &S, block_executed_class_hashes: &HashSet, tx_executed_class_hashes: &HashSet, ) -> TransactionExecutorResult { @@ -290,7 +294,7 @@ pub fn get_casm_hash_calculation_resources( let mut casm_hash_computation_resources = ExecutionResources::default(); for class_hash in newly_executed_class_hashes { - let class = state.get_compiled_contract_class(*class_hash)?; + let class = state_reader.get_compiled_contract_class(*class_hash)?; casm_hash_computation_resources += &class.estimate_casm_hash_computation_resources(); } diff --git a/crates/blockifier/src/bouncer_test.rs b/crates/blockifier/src/bouncer_test.rs index 6002e0421ae..8e454efe061 100644 --- a/crates/blockifier/src/bouncer_test.rs +++ b/crates/blockifier/src/bouncer_test.rs @@ -14,7 +14,7 @@ use crate::blockifier::transaction_executor::{ use crate::bouncer::{Bouncer, BouncerWeights, BuiltinCount}; use crate::context::BlockContext; use crate::execution::call_info::ExecutionSummary; -use crate::state::cached_state::{CachedState, StateChangesKeys}; +use crate::state::cached_state::{StateChangesKeys, TransactionalState}; use crate::storage_key; use crate::test_utils::initial_test_state::test_state; use crate::transaction::errors::TransactionExecutionError; @@ -197,7 +197,7 @@ fn test_bouncer_try_update( use crate::transaction::objects::TransactionResources; let state = &mut test_state(&BlockContext::create_for_account_testing().chain_info, 0, &[]); - let mut transactional_state = CachedState::create_transactional(state); + let mut transactional_state = TransactionalState::create_transactional(state); // Setup the bouncer. let block_max_capacity = BouncerWeights { @@ -257,9 +257,15 @@ fn test_bouncer_try_update( }, ..Default::default() }; + let tx_state_changes_keys = transactional_state.get_actual_state_changes().unwrap().into_keys(); // Try to update the bouncer. - let result = bouncer.try_update(&mut transactional_state, &execution_summary, &tx_resources); + let result = bouncer.try_update( + &transactional_state, + &tx_state_changes_keys, + &execution_summary, + &tx_resources, + ); // TODO(yael 27/3/24): compare the results without using string comparison. assert_eq!(format!("{:?}", result), format!("{:?}", expected_result)); diff --git a/crates/blockifier/src/concurrency.rs b/crates/blockifier/src/concurrency.rs index 13c9d244f02..3c0acb5a768 100644 --- a/crates/blockifier/src/concurrency.rs +++ b/crates/blockifier/src/concurrency.rs @@ -1,7 +1,10 @@ +pub mod fee_utils; pub mod scheduler; #[cfg(any(feature = "testing", test))] pub mod test_utils; -pub mod versioned_state_proxy; +pub mod utils; +pub mod versioned_state; pub mod versioned_storage; +pub mod worker_logic; type TxIndex = usize; diff --git a/crates/blockifier/src/concurrency/fee_utils.rs b/crates/blockifier/src/concurrency/fee_utils.rs new file mode 100644 index 00000000000..85d01871ca4 --- /dev/null +++ b/crates/blockifier/src/concurrency/fee_utils.rs @@ -0,0 +1,30 @@ +use starknet_api::hash::StarkFelt; + +use crate::execution::call_info::CallInfo; +#[cfg(test)] +#[path = "fee_utils_test.rs"] +mod test; + +// We read account balance (sender), and sequencer balance (recipient). The balance is of type +// `Uint256`, consist of two felts (lsb, msb). Hence, storage read values = +// [account_balance, 0, sequencer_balance, 0] +const STORAGE_READ_SEQUENCER_BALANCE_INDICES: (usize, usize) = (2, 3); + +// Completes the fee transfer execution by fixing the call info to have the correct sequencer +// balance. In concurrency mode, the fee transfer is executed with a false (constant) sequencer +// balance. This affects the call info. +pub fn fill_sequencer_balance_reads( + fee_transfer_call_info: &mut CallInfo, + sequencer_balance_low: StarkFelt, + sequencer_balance_high: StarkFelt, +) { + let storage_read_values = &mut fee_transfer_call_info.storage_read_values; + assert_eq!(storage_read_values.len(), 4, "Storage read values should have 4 elements"); + + let (low_index, high_index) = STORAGE_READ_SEQUENCER_BALANCE_INDICES; + for index in [low_index, high_index] { + assert_eq!(storage_read_values[index], StarkFelt::ZERO, "Sequencer balance should be zero"); + } + storage_read_values[low_index] = sequencer_balance_low; + storage_read_values[high_index] = sequencer_balance_high; +} diff --git a/crates/blockifier/src/concurrency/fee_utils_test.rs b/crates/blockifier/src/concurrency/fee_utils_test.rs new file mode 100644 index 00000000000..0774f9304fe --- /dev/null +++ b/crates/blockifier/src/concurrency/fee_utils_test.rs @@ -0,0 +1,48 @@ +use rstest::rstest; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::TransactionVersion; + +use crate::concurrency::fee_utils::fill_sequencer_balance_reads; +use crate::concurrency::test_utils::create_fee_transfer_call_info; +use crate::context::BlockContext; +use crate::invoke_tx_args; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::initial_test_state::{fund_account, test_state}; +use crate::test_utils::{ + create_trivial_calldata, + CairoVersion, + BALANCE, + MAX_L1_GAS_AMOUNT, + MAX_L1_GAS_PRICE, +}; +use crate::transaction::test_utils::{account_invoke_tx, block_context, l1_resource_bounds}; + +#[rstest] +pub fn test_fill_sequencer_balance_reads(block_context: BlockContext) { + let account = FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1); + let account_tx = account_invoke_tx(invoke_tx_args! { + sender_address: account.get_instance_address(0), + calldata: create_trivial_calldata(account.get_instance_address(0)), + resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), + version: TransactionVersion::THREE + }); + let chain_info = &block_context.chain_info; + let state = &mut test_state(chain_info, BALANCE, &[(account, 1)]); + + let sequencer_balance = 100; + let sequencer_address = block_context.block_info.sequencer_address; + fund_account(chain_info, sequencer_address, sequencer_balance, &mut state.state); + + let mut concurrency_call_info = create_fee_transfer_call_info(state, &account_tx, true); + let call_info = create_fee_transfer_call_info(state, &account_tx, false); + + assert_ne!(concurrency_call_info, call_info); + + fill_sequencer_balance_reads( + &mut concurrency_call_info, + StarkFelt::from(sequencer_balance), + StarkFelt::ZERO, + ); + + assert_eq!(concurrency_call_info, call_info); +} diff --git a/crates/blockifier/src/concurrency/scheduler.rs b/crates/blockifier/src/concurrency/scheduler.rs index 6ed5e65817e..0ca152ae16f 100644 --- a/crates/blockifier/src/concurrency/scheduler.rs +++ b/crates/blockifier/src/concurrency/scheduler.rs @@ -1,40 +1,73 @@ use std::cmp::min; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; -use std::sync::Mutex; +use std::sync::{Mutex, MutexGuard, TryLockError}; +use crate::concurrency::utils::lock_mutex_in_array; use crate::concurrency::TxIndex; #[cfg(test)] #[path = "scheduler_test.rs"] pub mod test; -// TODO(Avi, 01/04/2024): Remove dead_code attribute. -#[allow(dead_code)] +pub struct TransactionCommitter<'a> { + scheduler: &'a Scheduler, + commit_index_guard: MutexGuard<'a, usize>, +} + +impl<'a> TransactionCommitter<'a> { + pub fn new(scheduler: &'a Scheduler, commit_index_guard: MutexGuard<'a, usize>) -> Self { + Self { scheduler, commit_index_guard } + } + + /// Tries to commit the next uncommitted transaction in the chunk. Returns the index of the + /// transaction to commit if successful, or None if the transaction is not yet executed. + pub fn try_commit(&mut self) -> Option { + if *self.commit_index_guard >= self.scheduler.chunk_size { + return None; + }; + + let mut status = self.scheduler.lock_tx_status(*self.commit_index_guard); + if *status != TransactionStatus::Executed { + return None; + } + *status = TransactionStatus::Committed; + *self.commit_index_guard += 1; + if *self.commit_index_guard == self.scheduler.chunk_size { + self.scheduler.done_marker.store(true, Ordering::Release); + } + Some(*self.commit_index_guard - 1) + } + + /// Halts the scheduler. Decrements the commit index to indicate that the final transaction to + /// commit has been excluded from the block. + pub fn halt_scheduler(&mut self) { + assert!(*self.commit_index_guard > 0, "Commit index underflow."); + *self.commit_index_guard -= 1; + + self.scheduler.done_marker.store(true, Ordering::Release); + } +} + #[derive(Debug, Default)] pub struct Scheduler { execution_index: AtomicUsize, validation_index: AtomicUsize, - /// Read twice upon checking the chunk completion. Used to detect if validation or execution - /// index decreased from their observed values after ensuring that the number of active tasks - /// is zero. - decrease_counter: AtomicUsize, - n_active_tasks: AtomicUsize, + // The index of the next transaction to commit. + commit_index: Mutex, chunk_size: usize, + // TODO(Avi, 15/05/2024): Consider using RwLock instead of Mutex. tx_statuses: Box<[Mutex]>, - /// Updated by the `check_done` procedure, providing a cheap way for all threads to exit their - /// main loops. + // Set to true when all transactions have been committed, or when calling the halt_scheduler + // procedure, providing a cheap way for all threads to exit their main loops. done_marker: AtomicBool, } -// TODO(Avi, 01/04/2024): Remove dead_code attribute. -#[allow(dead_code)] impl Scheduler { pub fn new(chunk_size: usize) -> Scheduler { Scheduler { execution_index: AtomicUsize::new(0), validation_index: AtomicUsize::new(chunk_size), - decrease_counter: AtomicUsize::new(0), - n_active_tasks: AtomicUsize::new(0), + commit_index: Mutex::new(0), chunk_size, tx_statuses: std::iter::repeat_with(|| Mutex::new(TransactionStatus::ReadyToExecute)) .take(chunk_size) @@ -43,31 +76,6 @@ impl Scheduler { } } - /// Returns the done marker. - pub fn done(&self) -> bool { - self.done_marker.load(Ordering::Acquire) - } - - /// Checks if all transactions have been executed and validated. - fn check_done(&self) { - let observed_decrease_counter = self.decrease_counter.load(Ordering::Acquire); - - if min( - self.validation_index.load(Ordering::Acquire), - self.execution_index.load(Ordering::Acquire), - ) >= self.chunk_size - && self.n_active_tasks.load(Ordering::Acquire) == 0 - && observed_decrease_counter == self.decrease_counter.load(Ordering::Acquire) - { - self.done_marker.store(true, Ordering::Release); - } - } - - fn safe_decrement_n_active_tasks(&self) { - let previous_n_active_tasks = self.n_active_tasks.fetch_sub(1, Ordering::SeqCst); - assert!(previous_n_active_tasks > 0, "n_active_tasks underflow"); - } - pub fn next_task(&self) -> Task { if self.done() { return Task::Done; @@ -93,73 +101,143 @@ impl Scheduler { Task::NoTask } - // TODO(barak, 01/04/2024): Ensure documentation matches logic. /// Updates the Scheduler that an execution task has been finished and triggers the creation of /// new tasks accordingly: schedules validation for the current and higher transactions, if not /// already scheduled. - pub fn finish_execution(&self) -> Task { - todo!() + pub fn finish_execution(&self, tx_index: TxIndex) { + self.set_executed_status(tx_index); + if self.validation_index.load(Ordering::Acquire) > tx_index { + self.decrease_validation_index(tx_index); + } } - // TODO(barak, 01/04/2024): Ensure documentation matches logic. - /// Updates the Scheduler that a validation task has been finished and triggers the creation of - /// new tasks in case of failure: schedules validation for higher transactions + re-executes the - /// current transaction (if ready). - pub fn finish_validation(&self) -> Task { - todo!() + pub fn try_validation_abort(&self, tx_index: TxIndex) -> bool { + let mut status = self.lock_tx_status(tx_index); + if *status == TransactionStatus::Executed { + *status = TransactionStatus::Aborting; + return true; + } + false } - fn decrease_validation_index(&self, target_index: TxIndex) { - let previous_validation_index = - self.validation_index.fetch_min(target_index, Ordering::SeqCst); - if target_index < previous_validation_index { - self.decrease_counter.fetch_add(1, Ordering::SeqCst); + /// Updates the Scheduler that a validation task has aborted and triggers the creation of new + /// tasks: schedules validation for higher transactions + re-executes the current transaction + /// (if ready). + pub fn finish_abort(&self, tx_index: TxIndex) -> Task { + self.set_ready_status(tx_index); + if self.execution_index.load(Ordering::Acquire) > tx_index && self.try_incarnate(tx_index) { + Task::ExecutionTask(tx_index) + } else { + Task::NoTask } } - fn decrease_execution_index(&self, target_index: TxIndex) { - let previous_execution_index = - self.execution_index.fetch_min(target_index, Ordering::SeqCst); - if target_index < previous_execution_index { - self.decrease_counter.fetch_add(1, Ordering::SeqCst); + /// This method is called after a transaction gets re-executed during a commit. It decreases the + /// validation index to ensure that higher transactions are validated. There is no need to set + /// the transaction status to Executed, as it is already set to Committed. + // TODO(Meshi, 01/06/2024): Add a call to this method after re-executing a transaction during + // commit. + pub fn finish_execution_during_commit(&self, tx_index: TxIndex) { + self.decrease_validation_index(tx_index + 1); + } + + /// Tries to takes the lock on the commit index. Returns a `TransactionCommitter` if successful, + /// or None if the lock is already taken. + pub fn try_enter_commit_phase(&self) -> Option> { + match self.commit_index.try_lock() { + Ok(guard) => Some(TransactionCommitter::new(self, guard)), + Err(TryLockError::WouldBlock) => None, + Err(TryLockError::Poisoned(error)) => { + panic!("Commit index is poisoned. Data: {:?}.", *error.get_ref()) + } } } + fn lock_tx_status(&self, tx_index: TxIndex) -> MutexGuard<'_, TransactionStatus> { + lock_mutex_in_array(&self.tx_statuses, tx_index) + } + + fn set_executed_status(&self, tx_index: TxIndex) { + let mut status = self.lock_tx_status(tx_index); + assert_eq!( + *status, + TransactionStatus::Executing, + "Only executing transactions can gain status executed. Transaction {tx_index} is not \ + executing. Transaction status: {status:?}." + ); + *status = TransactionStatus::Executed; + } + + fn set_ready_status(&self, tx_index: TxIndex) { + let mut status = self.lock_tx_status(tx_index); + assert_eq!( + *status, + TransactionStatus::Aborting, + "Only aborting transactions can be re-executed. Transaction {tx_index} is not \ + aborting. Transaction status: {status:?}." + ); + *status = TransactionStatus::ReadyToExecute; + } + + fn decrease_validation_index(&self, target_index: TxIndex) { + self.validation_index.fetch_min(target_index, Ordering::SeqCst); + } + /// Updates a transaction's status to `Executing` if it is ready to execute. - fn try_incarnate(&self, tx_index: TxIndex) -> Option { + fn try_incarnate(&self, tx_index: TxIndex) -> bool { if tx_index < self.chunk_size { - // TODO(barak, 01/04/2024): complete try_incarnate logic. - return Some(tx_index); + let mut status = self.lock_tx_status(tx_index); + if *status == TransactionStatus::ReadyToExecute { + *status = TransactionStatus::Executing; + return true; + } } - self.safe_decrement_n_active_tasks(); - None + false } fn next_version_to_validate(&self) -> Option { let index_to_validate = self.validation_index.load(Ordering::Acquire); if index_to_validate >= self.chunk_size { - self.check_done(); return None; } - self.n_active_tasks.fetch_add(1, Ordering::SeqCst); let index_to_validate = self.validation_index.fetch_add(1, Ordering::SeqCst); if index_to_validate < self.chunk_size { - // TODO(barak, 01/04/2024): complete next_version_to_validate logic. - return Some(index_to_validate); + let status = self.lock_tx_status(index_to_validate); + if *status == TransactionStatus::Executed { + return Some(index_to_validate); + } } - self.safe_decrement_n_active_tasks(); None } fn next_version_to_execute(&self) -> Option { let index_to_execute = self.execution_index.load(Ordering::Acquire); if index_to_execute >= self.chunk_size { - self.check_done(); return None; } - self.n_active_tasks.fetch_add(1, Ordering::SeqCst); let index_to_execute = self.execution_index.fetch_add(1, Ordering::SeqCst); - self.try_incarnate(index_to_execute) + if self.try_incarnate(index_to_execute) { + return Some(index_to_execute); + } + None + } + + /// Returns the done marker. + fn done(&self) -> bool { + self.done_marker.load(Ordering::Acquire) + } + + #[cfg(any(feature = "testing", test))] + pub fn set_tx_status(&self, tx_index: TxIndex, status: TransactionStatus) { + if tx_index < self.chunk_size { + let mut tx_status = self.lock_tx_status(tx_index); + *tx_status = status; + } + } + + #[cfg(any(feature = "testing", test))] + pub fn get_tx_status(&self, tx_index: TxIndex) -> MutexGuard<'_, TransactionStatus> { + self.lock_tx_status(tx_index) } } @@ -171,12 +249,11 @@ pub enum Task { Done, } -// TODO(Barak, 01/04/2024): Remove dead_code attribute. -#[allow(dead_code)] #[derive(Clone, Copy, Debug, PartialEq)] -enum TransactionStatus { +pub enum TransactionStatus { ReadyToExecute, Executing, Executed, Aborting, + Committed, } diff --git a/crates/blockifier/src/concurrency/scheduler_test.rs b/crates/blockifier/src/concurrency/scheduler_test.rs index d821c361b06..33284aa1532 100644 --- a/crates/blockifier/src/concurrency/scheduler_test.rs +++ b/crates/blockifier/src/concurrency/scheduler_test.rs @@ -1,5 +1,6 @@ use std::cmp::min; use std::sync::atomic::Ordering; +use std::sync::Arc; use pretty_assertions::assert_eq; use rstest::rstest; @@ -15,8 +16,7 @@ fn test_new(#[values(0, 1, 32)] chunk_size: usize) { let scheduler = Scheduler::new(chunk_size); assert_eq!(scheduler.execution_index.into_inner(), 0); assert_eq!(scheduler.validation_index.into_inner(), chunk_size); - assert_eq!(scheduler.decrease_counter.into_inner(), 0); - assert_eq!(scheduler.n_active_tasks.into_inner(), 0); + assert_eq!(*scheduler.commit_index.lock().unwrap(), 0); assert_eq!(scheduler.chunk_size, chunk_size); assert_eq!(scheduler.tx_statuses.len(), chunk_size); for i in 0..chunk_size { @@ -26,140 +26,292 @@ fn test_new(#[values(0, 1, 32)] chunk_size: usize) { } #[rstest] -#[case::done(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, 0, true)] -#[case::active_tasks(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, 1, false)] -#[case::execution_incomplete(DEFAULT_CHUNK_SIZE-1, DEFAULT_CHUNK_SIZE+1, 0, false)] -#[case::validation_incomplete(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE-1, 0, false)] -fn test_check_done( - #[case] execution_index: usize, - #[case] validation_index: usize, - #[case] n_active_tasks: usize, - #[case] expected: bool, +fn test_lock_tx_status() { + let scheduler = Scheduler::new(DEFAULT_CHUNK_SIZE); + let status = scheduler.lock_tx_status(0); + assert_eq!(*status, TransactionStatus::ReadyToExecute); +} + +#[rstest] +#[should_panic(expected = "Cell of transaction index 0 is poisoned. Data: ReadyToExecute.")] +fn test_lock_tx_status_poisoned() { + let scheduler = Arc::new(Scheduler::new(DEFAULT_CHUNK_SIZE)); + let scheduler_clone = scheduler.clone(); + let handle = std::thread::spawn(move || { + let _guard = scheduler_clone.lock_tx_status(0); + panic!("Intentional panic to poison the mutex") + }); + handle.join().expect_err("Thread did not panic as expected"); + // The panic is expected here. + let _guard = scheduler.lock_tx_status(0); +} + +#[rstest] +#[case::done(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, TransactionStatus::Executed, Task::Done)] +#[case::no_task(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, TransactionStatus::Executed, Task::NoTask)] +#[case::no_task_as_validation_index_not_executed( + DEFAULT_CHUNK_SIZE, + 0, + TransactionStatus::ReadyToExecute, + Task::NoTask +)] +#[case::execution_task(0, 0, TransactionStatus::ReadyToExecute, Task::ExecutionTask(0))] +#[case::execution_task_as_validation_index_not_executed( + 1, + 0, + TransactionStatus::ReadyToExecute, + Task::ExecutionTask(1) +)] +#[case::validation_task(1, 0, TransactionStatus::Executed, Task::ValidationTask(0))] +fn test_next_task( + #[case] execution_index: TxIndex, + #[case] validation_index: TxIndex, + #[case] validation_index_status: TransactionStatus, + #[case] expected_next_task: Task, ) { let scheduler = default_scheduler!( chunk_size: DEFAULT_CHUNK_SIZE, execution_index: execution_index, validation_index: validation_index, - n_active_tasks: n_active_tasks + done_marker: expected_next_task == Task::Done, ); - scheduler.check_done(); - assert_eq!(scheduler.done_marker.load(Ordering::Acquire), expected); + scheduler.set_tx_status(validation_index, validation_index_status); + let next_task = scheduler.next_task(); + assert_eq!(next_task, expected_next_task); } #[rstest] -#[case::happy_flow(1, 0)] -#[should_panic(expected = "n_active_tasks underflow")] -#[case::underflow(0, 0)] -fn test_safe_decrement_n_active_tasks( - #[case] n_active_tasks: usize, - #[case] expected_n_active_tasks: usize, +#[case::happy_flow(0, TransactionStatus::Executed, false)] +#[case::happy_flow_with_halt(0, TransactionStatus::Executed, true)] +#[case::last_transaction_to_commit(DEFAULT_CHUNK_SIZE - 1, TransactionStatus::Executed, false)] +#[case::last_transaction_to_commit_with_halt(DEFAULT_CHUNK_SIZE - 1, TransactionStatus::Executed, true)] +#[case::wrong_status_ready(0, TransactionStatus::ReadyToExecute, false)] +#[case::wrong_status_executing(0, TransactionStatus::Executing, false)] +#[case::wrong_status_aborting(0, TransactionStatus::Aborting, false)] +#[case::wrong_status_committed(0, TransactionStatus::Committed, false)] +fn test_commit_flow( + #[case] commit_index: TxIndex, + #[case] commit_index_tx_status: TransactionStatus, + #[case] should_halt: bool, ) { - let scheduler = - default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, n_active_tasks: n_active_tasks); - scheduler.safe_decrement_n_active_tasks(); - assert_eq!(scheduler.n_active_tasks.load(Ordering::Acquire), expected_n_active_tasks); + let scheduler = default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, commit_index: commit_index); + scheduler.set_tx_status(commit_index, commit_index_tx_status); + let mut transaction_committer = scheduler.try_enter_commit_phase().unwrap(); + // Lock is already acquired. + assert!(scheduler.try_enter_commit_phase().is_none()); + if let Some(index) = transaction_committer.try_commit() { + assert_eq!(index, commit_index); + } + if should_halt { + transaction_committer.halt_scheduler(); + } + drop(transaction_committer); + if commit_index_tx_status == TransactionStatus::Executed { + assert_eq!(*scheduler.lock_tx_status(commit_index), TransactionStatus::Committed); + assert_eq!( + *scheduler.commit_index.lock().unwrap(), + if should_halt { commit_index } else { commit_index + 1 } + ); + assert_eq!( + scheduler.done_marker.load(Ordering::Acquire), + commit_index + 1 == DEFAULT_CHUNK_SIZE || should_halt + ); + } else { + assert_eq!(*scheduler.lock_tx_status(commit_index), commit_index_tx_status); + assert_eq!(*scheduler.commit_index.lock().unwrap(), commit_index); + } } #[rstest] -#[case::done(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, true, Task::Done, 0)] -#[case::no_task(DEFAULT_CHUNK_SIZE, DEFAULT_CHUNK_SIZE, false, Task::NoTask, 0)] -#[case::execution_task(0, 0, false, Task::ExecutionTask(0), 1)] -#[case::validation_task(1, 0, false, Task::ValidationTask(0), 1)] -fn test_next_task( - #[case] execution_index: usize, - #[case] validation_index: usize, - #[case] done_marker: bool, - #[case] expected_next_task: Task, - #[case] expected_n_active_tasks: usize, +#[case::happy_flow(TransactionStatus::Executing)] +#[should_panic(expected = "Only executing transactions can gain status executed. Transaction 0 \ + is not executing. Transaction status: ReadyToExecute.")] +#[case::wrong_status_ready(TransactionStatus::ReadyToExecute)] +#[should_panic(expected = "Only executing transactions can gain status executed. Transaction 0 \ + is not executing. Transaction status: Executed.")] +#[case::wrong_status_executed(TransactionStatus::Executed)] +#[should_panic(expected = "Only executing transactions can gain status executed. Transaction 0 \ + is not executing. Transaction status: Aborting.")] +#[case::wrong_status_aborting(TransactionStatus::Aborting)] +#[should_panic(expected = "Only executing transactions can gain status executed. Transaction 0 \ + is not executing. Transaction status: Committed.")] +#[case::wrong_status_committed(TransactionStatus::Committed)] +fn test_set_executed_status(#[case] tx_status: TransactionStatus) { + let tx_index = 0; + let scheduler = Scheduler::new(DEFAULT_CHUNK_SIZE); + scheduler.set_tx_status(tx_index, tx_status); + // Panic is expected here in negative flows. + scheduler.set_executed_status(tx_index); + assert_eq!(*scheduler.lock_tx_status(tx_index), TransactionStatus::Executed); +} + +#[rstest] +#[case::reduces_validation_index(0, 10)] +#[case::does_not_reduce_validation_index(10, 0)] +fn test_finish_execution(#[case] tx_index: TxIndex, #[case] validation_index: TxIndex) { + let scheduler = default_scheduler!( + chunk_size: DEFAULT_CHUNK_SIZE, + validation_index: validation_index, + ); + scheduler.set_tx_status(tx_index, TransactionStatus::Executing); + scheduler.finish_execution(tx_index); + assert_eq!(*scheduler.lock_tx_status(tx_index), TransactionStatus::Executed); + assert_eq!(scheduler.validation_index.load(Ordering::Acquire), min(tx_index, validation_index)); +} + +#[rstest] +#[case::happy_flow(TransactionStatus::Aborting)] +#[should_panic(expected = "Only aborting transactions can be re-executed. Transaction 0 is not \ + aborting. Transaction status: ReadyToExecute.")] +#[case::wrong_status_ready(TransactionStatus::ReadyToExecute)] +#[should_panic(expected = "Only aborting transactions can be re-executed. Transaction 0 is not \ + aborting. Transaction status: Executed.")] +#[case::wrong_status_executed(TransactionStatus::Executed)] +#[should_panic(expected = "Only aborting transactions can be re-executed. Transaction 0 is not \ + aborting. Transaction status: Executing.")] +#[case::wrong_status_executing(TransactionStatus::Executing)] +#[should_panic(expected = "Only aborting transactions can be re-executed. Transaction 0 is not \ + aborting. Transaction status: Committed.")] +#[case::wrong_status_committed(TransactionStatus::Committed)] +fn test_set_ready_status(#[case] tx_status: TransactionStatus) { + let tx_index = 0; + let scheduler = Scheduler::new(DEFAULT_CHUNK_SIZE); + scheduler.set_tx_status(tx_index, tx_status); + // Panic is expected here in negative flows. + scheduler.set_ready_status(tx_index); + assert_eq!(*scheduler.lock_tx_status(tx_index), TransactionStatus::ReadyToExecute); +} + +#[rstest] +#[case::abort_validation(TransactionStatus::Executed)] +#[case::wrong_status_ready(TransactionStatus::ReadyToExecute)] +#[case::wrong_status_executing(TransactionStatus::Executing)] +#[case::wrong_status_aborted(TransactionStatus::Aborting)] +#[case::wrong_status_committed(TransactionStatus::Committed)] +fn test_try_validation_abort(#[case] tx_status: TransactionStatus) { + let tx_index = 0; + let scheduler = Scheduler::new(DEFAULT_CHUNK_SIZE); + scheduler.set_tx_status(tx_index, tx_status); + let result = scheduler.try_validation_abort(tx_index); + assert_eq!(result, tx_status == TransactionStatus::Executed); + if result { + assert_eq!(*scheduler.lock_tx_status(tx_index), TransactionStatus::Aborting); + } +} + +#[rstest] +#[case::not_aborted(0, 10, false)] +#[case::returns_execution_task(0, 10, true)] +#[case::does_not_return_execution_task(10, 0, true)] +fn test_finish_validation( + #[case] tx_index: TxIndex, + #[case] execution_index: TxIndex, + #[case] aborted: bool, ) { let scheduler = default_scheduler!( chunk_size: DEFAULT_CHUNK_SIZE, execution_index: execution_index, - validation_index: validation_index, - done_marker: done_marker, ); - let next_task = scheduler.next_task(); - assert_eq!(next_task, expected_next_task); - assert_eq!(scheduler.n_active_tasks.load(Ordering::Acquire), expected_n_active_tasks); + let tx_status = if aborted { TransactionStatus::Aborting } else { TransactionStatus::Executed }; + scheduler.set_tx_status(tx_index, tx_status); + let mut result = Task::NoTask; + if aborted { + result = scheduler.finish_abort(tx_index); + } + let new_status = scheduler.lock_tx_status(tx_index); + match aborted { + true => { + if execution_index > tx_index { + assert_eq!(result, Task::ExecutionTask(tx_index)); + assert_eq!(*new_status, TransactionStatus::Executing); + } else { + assert_eq!(result, Task::NoTask); + assert_eq!(*new_status, TransactionStatus::ReadyToExecute); + } + } + false => { + assert_eq!(result, Task::NoTask); + assert_eq!(*new_status, TransactionStatus::Executed); + } + } } #[rstest] -#[case::target_index_lt_validation_index(1, 3, 1)] -#[case::target_index_eq_validation_index(3, 3, 0)] -#[case::target_index_eq_validation_index_eq_zero(0, 0, 0)] -#[case::target_index_gt_validation_index(1, 0, 0)] +#[case::target_index_lt_validation_index(1, 3)] +#[case::target_index_eq_validation_index(3, 3)] +#[case::target_index_eq_validation_index_eq_zero(0, 0)] +#[case::target_index_gt_validation_index(1, 0)] fn test_decrease_validation_index( #[case] target_index: TxIndex, - #[case] validation_index: usize, - #[case] expected_decrease_counter: usize, + #[case] validation_index: TxIndex, ) { let scheduler = default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, validation_index: validation_index); scheduler.decrease_validation_index(target_index); let expected_validation_index = min(target_index, validation_index); assert_eq!(scheduler.validation_index.load(Ordering::Acquire), expected_validation_index); - assert_eq!(scheduler.decrease_counter.load(Ordering::Acquire), expected_decrease_counter); } #[rstest] -#[case::target_index_lt_execution_index(1, 3, 1)] -#[case::target_index_eq_execution_index(3, 3, 0)] -#[case::target_index_eq_execution_index_eq_zero(0, 0, 0)] -#[case::target_index_gt_execution_index(1, 0, 0)] -fn test_decrease_execution_index( - #[case] target_index: TxIndex, - #[case] execution_index: usize, - #[case] expected_decrease_counter: usize, -) { - let scheduler = - default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, execution_index: execution_index); - scheduler.decrease_execution_index(target_index); - let expected_execution_index = min(target_index, execution_index); - assert_eq!(scheduler.execution_index.load(Ordering::Acquire), expected_execution_index); - assert_eq!(scheduler.decrease_counter.load(Ordering::Acquire), expected_decrease_counter); -} - -#[rstest] -#[case::from_ready_to_execute_to_executing(0, Some(0), 1)] -#[case::index_out_of_bounds(DEFAULT_CHUNK_SIZE, None, 0)] +#[case::ready_to_execute(0, TransactionStatus::ReadyToExecute, true)] +#[case::executing(0, TransactionStatus::Executing, false)] +#[case::executed(0, TransactionStatus::Executed, false)] +#[case::aborting(0, TransactionStatus::Aborting, false)] +#[case::committed(0, TransactionStatus::Committed, false)] +#[case::index_out_of_bounds(DEFAULT_CHUNK_SIZE, TransactionStatus::ReadyToExecute, false)] fn test_try_incarnate( - #[case] tx_index: usize, - #[case] expected_output: Option, - #[case] expected_n_active_tasks: usize, + #[case] tx_index: TxIndex, + #[case] tx_status: TransactionStatus, + #[case] expected_output: bool, ) { - let scheduler = default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, n_active_tasks: 1); + let scheduler = Scheduler::new(DEFAULT_CHUNK_SIZE); + scheduler.set_tx_status(tx_index, tx_status); assert_eq!(scheduler.try_incarnate(tx_index), expected_output); - assert_eq!(scheduler.n_active_tasks.load(Ordering::Acquire), expected_n_active_tasks); + if expected_output { + assert_eq!(*scheduler.lock_tx_status(tx_index), TransactionStatus::Executing); + } else if tx_index < DEFAULT_CHUNK_SIZE { + assert_eq!(*scheduler.lock_tx_status(tx_index), tx_status); + } } #[rstest] -#[case::some(1, Some(1), 2, 1)] -#[case::none(DEFAULT_CHUNK_SIZE, None, DEFAULT_CHUNK_SIZE, 0)] +#[case::ready_to_execute(1, TransactionStatus::ReadyToExecute, None)] +#[case::executing(1, TransactionStatus::Executing, None)] +#[case::executed(1, TransactionStatus::Executed, Some(1))] +#[case::aborting(1, TransactionStatus::Aborting, None)] +#[case::committed(1, TransactionStatus::Committed, None)] +#[case::index_out_of_bounds(DEFAULT_CHUNK_SIZE, TransactionStatus::ReadyToExecute, None)] fn test_next_version_to_validate( - #[case] validation_index: usize, - #[case] expected_output: Option, - #[case] expected_validation_index: usize, - #[case] expected_n_active_tasks: usize, + #[case] validation_index: TxIndex, + #[case] tx_status: TransactionStatus, + #[case] expected_output: Option, ) { let scheduler = default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, validation_index: validation_index); + scheduler.set_tx_status(validation_index, tx_status); assert_eq!(scheduler.next_version_to_validate(), expected_output); + let expected_validation_index = + if validation_index < DEFAULT_CHUNK_SIZE { validation_index + 1 } else { validation_index }; assert_eq!(scheduler.validation_index.load(Ordering::Acquire), expected_validation_index); - assert_eq!(scheduler.n_active_tasks.load(Ordering::Acquire), expected_n_active_tasks); } #[rstest] -#[case::some(1, Some(1), 2, 1)] -#[case::none(DEFAULT_CHUNK_SIZE, None, DEFAULT_CHUNK_SIZE, 0)] +#[case::ready_to_execute(1, TransactionStatus::ReadyToExecute, Some(1))] +#[case::executing(1, TransactionStatus::Executing, None)] +#[case::executed(1, TransactionStatus::Executed, None)] +#[case::aborting(1, TransactionStatus::Aborting, None)] +#[case::committed(1, TransactionStatus::Committed, None)] +#[case::index_out_of_bounds(DEFAULT_CHUNK_SIZE, TransactionStatus::ReadyToExecute, None)] fn test_next_version_to_execute( - #[case] execution_index: usize, - #[case] expected_output: Option, - #[case] expected_execution_index: usize, - #[case] expected_n_active_tasks: usize, + #[case] execution_index: TxIndex, + #[case] tx_status: TransactionStatus, + #[case] expected_output: Option, ) { let scheduler = default_scheduler!(chunk_size: DEFAULT_CHUNK_SIZE, execution_index: execution_index); + scheduler.set_tx_status(execution_index, tx_status); assert_eq!(scheduler.next_version_to_execute(), expected_output); + let expected_execution_index = + if execution_index < DEFAULT_CHUNK_SIZE { execution_index + 1 } else { execution_index }; assert_eq!(scheduler.execution_index.load(Ordering::Acquire), expected_execution_index); - assert_eq!(scheduler.n_active_tasks.load(Ordering::Acquire), expected_n_active_tasks); } diff --git a/crates/blockifier/src/concurrency/test_utils.rs b/crates/blockifier/src/concurrency/test_utils.rs index b7439b878c5..3df10eb6ddb 100644 --- a/crates/blockifier/src/concurrency/test_utils.rs +++ b/crates/blockifier/src/concurrency/test_utils.rs @@ -1,7 +1,30 @@ -use std::sync::{Arc, Mutex}; +use rstest::fixture; +use starknet_api::core::{ClassHash, ContractAddress, PatriciaKey}; +use starknet_api::hash::StarkHash; +use starknet_api::{class_hash, contract_address, patricia_key}; -use crate::concurrency::versioned_state_proxy::VersionedState; +use crate::concurrency::versioned_state::{ThreadSafeVersionedState, VersionedState}; +use crate::context::BlockContext; +use crate::execution::call_info::CallInfo; +use crate::state::cached_state::{CachedState, TransactionalState}; use crate::state::state_api::StateReader; +use crate::test_utils::dict_state_reader::DictStateReader; +use crate::transaction::account_transaction::AccountTransaction; +use crate::transaction::transactions::ExecutableTransaction; + +// Fixtures. + +#[fixture] +pub fn contract_address() -> ContractAddress { + contract_address!("0x18031991") +} + +#[fixture] +pub fn class_hash() -> ClassHash { + class_hash!(27_u8) +} + +// Macros. #[macro_export] macro_rules! default_scheduler { @@ -32,8 +55,28 @@ macro_rules! default_scheduler { } // TODO(meshi, 01/06/2024): Consider making this a macro. -pub fn versioned_state_for_testing( - block_state: impl StateReader, -) -> Arc>> { - Arc::new(Mutex::new(VersionedState::new(block_state))) +pub fn safe_versioned_state_for_testing( + block_state: DictStateReader, +) -> ThreadSafeVersionedState { + ThreadSafeVersionedState::new(VersionedState::new(block_state)) +} + +// Note: this function does not mutate the state. +pub fn create_fee_transfer_call_info( + state: &mut CachedState, + account_tx: &AccountTransaction, + concurrency_mode: bool, +) -> CallInfo { + let block_context = + BlockContext::create_for_account_testing_with_concurrency_mode(concurrency_mode); + let mut transactional_state = TransactionalState::create_transactional(state); + let charge_fee = true; + let validate = true; + let execution_info = account_tx + .execute_raw(&mut transactional_state, &block_context, charge_fee, validate) + .unwrap(); + + let execution_info = execution_info.fee_transfer_call_info.unwrap(); + transactional_state.abort(); + execution_info } diff --git a/crates/blockifier/src/concurrency/utils.rs b/crates/blockifier/src/concurrency/utils.rs new file mode 100644 index 00000000000..4ca2b2eb178 --- /dev/null +++ b/crates/blockifier/src/concurrency/utils.rs @@ -0,0 +1,10 @@ +use std::fmt::Debug; +use std::sync::{Mutex, MutexGuard}; + +use crate::concurrency::TxIndex; + +pub fn lock_mutex_in_array(array: &[Mutex], tx_index: TxIndex) -> MutexGuard<'_, T> { + array[tx_index].lock().unwrap_or_else(|error| { + panic!("Cell of transaction index {} is poisoned. Data: {:?}.", tx_index, *error.get_ref()) + }) +} diff --git a/crates/blockifier/src/concurrency/versioned_state_proxy.rs b/crates/blockifier/src/concurrency/versioned_state.rs similarity index 69% rename from crates/blockifier/src/concurrency/versioned_state_proxy.rs rename to crates/blockifier/src/concurrency/versioned_state.rs index 7350080207f..b1b3467f887 100644 --- a/crates/blockifier/src/concurrency/versioned_state_proxy.rs +++ b/crates/blockifier/src/concurrency/versioned_state.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex, MutexGuard}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; @@ -9,11 +9,11 @@ use crate::concurrency::versioned_storage::VersionedStorage; use crate::concurrency::TxIndex; use crate::execution::contract_class::ContractClass; use crate::state::cached_state::{CachedState, ContractClassMapping, StateMaps}; -use crate::state::state_api::{StateReader, StateResult}; +use crate::state::state_api::{StateReader, StateResult, UpdatableState}; #[cfg(test)] -#[path = "versioned_state_proxy_test.rs"] -pub mod test; +#[path = "versioned_state_test.rs"] +pub mod versioned_state_test; const READ_ERR: &str = "Error: read value missing in the versioned storage"; @@ -21,6 +21,7 @@ const READ_ERR: &str = "Error: read value missing in the versioned storage"; /// Represents a versioned state used as shared state between a chunk of workers. /// This state facilitates concurrent operations. /// Reader functionality is injected through initial state. +#[derive(Debug)] pub struct VersionedState { initial_state: S, storage: VersionedStorage<(ContractAddress, StorageKey), StarkFelt>, @@ -42,12 +43,25 @@ impl VersionedState { } } - pub fn get_writes(&mut self, from_index: TxIndex) -> StateMaps { + fn get_writes_up_to_index(&mut self, tx_index: TxIndex) -> StateMaps { StateMaps { - storage: self.storage.get_writes_from_index(from_index), - nonces: self.nonces.get_writes_from_index(from_index), - class_hashes: self.class_hashes.get_writes_from_index(from_index), - compiled_class_hashes: self.compiled_class_hashes.get_writes_from_index(from_index), + storage: self.storage.get_writes_up_to_index(tx_index), + nonces: self.nonces.get_writes_up_to_index(tx_index), + class_hashes: self.class_hashes.get_writes_up_to_index(tx_index), + compiled_class_hashes: self.compiled_class_hashes.get_writes_up_to_index(tx_index), + // TODO(OriF, 01/07/2024): Update declared_contracts initial value. + declared_contracts: HashMap::new(), + } + } + + #[cfg(any(feature = "testing", test))] + pub fn get_writes_of_index(&self, tx_index: TxIndex) -> StateMaps { + StateMaps { + storage: self.storage.get_writes_of_index(tx_index), + nonces: self.nonces.get_writes_of_index(tx_index), + class_hashes: self.class_hashes.get_writes_of_index(tx_index), + compiled_class_hashes: self.compiled_class_hashes.get_writes_of_index(tx_index), + // TODO(OriF, 01/07/2024): Update declared_contracts initial value. declared_contracts: HashMap::new(), } } @@ -56,18 +70,20 @@ impl VersionedState { where T: StateReader, { - let writes = self.get_writes(from_index); - parent_state.update_cache(writes); + let writes = self.get_writes_up_to_index(from_index); + parent_state.update_cache(&writes); parent_state.update_contract_class_cache( - self.compiled_contract_classes.get_writes_from_index(from_index), + self.compiled_contract_classes.get_writes_up_to_index(from_index), ); } // TODO(Mohammad, 01/04/2024): Store the read set (and write set) within a shared // object (probabily `VersionedState`). As RefCell operations are not thread-safe. Therefore, // accessing this function should be protected by a mutex to ensure thread safety. - pub fn validate_read_set(&mut self, tx_index: TxIndex, reads: &StateMaps) -> bool { + // TODO: Consider coupling the tx index with the read set to ensure any mismatch between them + // will cause the validation to fail. + fn validate_reads(&mut self, tx_index: TxIndex, reads: &StateMaps) -> bool { // If is the first transaction in the chunk, then the read set is valid. Since it has no // predecessors, there's nothing to compare it to. if tx_index == 0 { @@ -115,7 +131,7 @@ impl VersionedState { true } - pub fn apply_writes( + fn apply_writes( &mut self, tx_index: TxIndex, writes: &StateMaps, @@ -137,26 +153,80 @@ impl VersionedState { self.compiled_contract_classes.write(tx_index, key, value.clone()); } } + + fn delete_writes( + &mut self, + tx_index: TxIndex, + writes: &StateMaps, + class_hash_to_class: &ContractClassMapping, + ) { + for &key in writes.storage.keys() { + self.storage.delete_write(key, tx_index); + } + for &key in writes.nonces.keys() { + self.nonces.delete_write(key, tx_index); + } + for &key in writes.class_hashes.keys() { + self.class_hashes.delete_write(key, tx_index); + } + for &key in writes.compiled_class_hashes.keys() { + self.compiled_class_hashes.delete_write(key, tx_index); + } + // TODO(OriF, 01/07/2024): Add a for loop for `declared_contracts`. + for &key in class_hash_to_class.keys() { + self.compiled_contract_classes.delete_write(key, tx_index); + } + } } pub struct ThreadSafeVersionedState(Arc>>); pub type LockedVersionedState<'a, S> = MutexGuard<'a, VersionedState>; impl ThreadSafeVersionedState { + pub fn new(versioned_state: VersionedState) -> Self { + ThreadSafeVersionedState(Arc::new(Mutex::new(versioned_state))) + } + pub fn pin_version(&self, tx_index: TxIndex) -> VersionedStateProxy { VersionedStateProxy { tx_index, state: self.0.clone() } } } +impl Clone for ThreadSafeVersionedState { + fn clone(&self) -> Self { + ThreadSafeVersionedState(Arc::clone(&self.0)) + } +} + pub struct VersionedStateProxy { pub tx_index: TxIndex, pub state: Arc>>, } impl VersionedStateProxy { - pub fn state(&self) -> LockedVersionedState<'_, S> { + fn state(&self) -> LockedVersionedState<'_, S> { self.state.lock().expect("Failed to acquire state lock.") } + + pub fn validate_reads(&self, reads: &StateMaps) -> bool { + self.state().validate_reads(self.tx_index, reads) + } + + pub fn delete_writes(&self, writes: &StateMaps, class_hash_to_class: &ContractClassMapping) { + self.state().delete_writes(self.tx_index, writes, class_hash_to_class); + } +} + +// TODO(OriF, 15/5/24): Consider using visited_pcs. +impl UpdatableState for VersionedStateProxy { + fn apply_writes( + &mut self, + writes: &StateMaps, + class_hash_to_class: &ContractClassMapping, + _visited_pcs: &HashMap>, + ) { + self.state().apply_writes(self.tx_index, writes, class_hash_to_class) + } } impl StateReader for VersionedStateProxy { diff --git a/crates/blockifier/src/concurrency/versioned_state_proxy_test.rs b/crates/blockifier/src/concurrency/versioned_state_proxy_test.rs deleted file mode 100644 index f7edcc2b8a5..00000000000 --- a/crates/blockifier/src/concurrency/versioned_state_proxy_test.rs +++ /dev/null @@ -1,244 +0,0 @@ -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use std::thread; - -use starknet_api::core::{calculate_contract_address, ClassHash, ContractAddress, PatriciaKey}; -use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::transaction::{Calldata, ContractAddressSalt, Fee, TransactionVersion}; -use starknet_api::{calldata, class_hash, contract_address, patricia_key, stark_felt}; - -use crate::abi::abi_utils::{get_fee_token_var_address, get_storage_var_address}; -use crate::concurrency::versioned_state_proxy::{ - ThreadSafeVersionedState, - VersionedState, - VersionedStateProxy, -}; -use crate::context::BlockContext; -use crate::state::cached_state::{CachedState, StateMaps}; -use crate::state::state_api::{State, StateReader}; -use crate::test_utils::contracts::FeatureContract; -use crate::test_utils::deploy_account::deploy_account_tx; -use crate::test_utils::dict_state_reader::DictStateReader; -use crate::test_utils::initial_test_state::test_state; -use crate::test_utils::{CairoVersion, NonceManager, BALANCE, DEFAULT_STRK_L1_GAS_PRICE, MAX_FEE}; -use crate::transaction::account_transaction::AccountTransaction; -use crate::transaction::objects::{FeeType, TransactionInfoCreator}; -use crate::transaction::test_utils::l1_resource_bounds; -use crate::transaction::transactions::ExecutableTransaction; -use crate::{compiled_class_hash, deploy_account_tx_args, nonce, storage_key}; - -#[test] -fn test_versioned_state_proxy() { - // Test data - let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); - let contract_address = contract_address!("0x1"); - let key = storage_key!("0x10"); - let stark_felt = stark_felt!(13_u8); - let nonce = nonce!(2_u8); - let class_hash = class_hash!(27_u8); - let compiled_class_hash = compiled_class_hash!(29_u8); - let contract_class = test_contract.get_class(); - - // Create the verioned state - let cached_state = CachedState::from(DictStateReader { - storage_view: HashMap::from([((contract_address, key), stark_felt)]), - address_to_nonce: HashMap::from([(contract_address, nonce)]), - address_to_class_hash: HashMap::from([(contract_address, class_hash)]), - class_hash_to_compiled_class_hash: HashMap::from([(class_hash, compiled_class_hash)]), - class_hash_to_class: HashMap::from([(class_hash, contract_class.clone())]), - }); - - let versioned_state = Arc::new(Mutex::new(VersionedState::new(cached_state))); - - let safe_versioned_state = ThreadSafeVersionedState(Arc::clone(&versioned_state)); - let versioned_state_proxys: Vec>> = - (0..20).map(|i| safe_versioned_state.pin_version(i)).collect(); - - // Read initial data - assert_eq!(versioned_state_proxys[5].get_nonce_at(contract_address).unwrap(), nonce); - assert_eq!(versioned_state_proxys[0].get_nonce_at(contract_address).unwrap(), nonce); - assert_eq!( - versioned_state_proxys[7].get_storage_at(contract_address, key).unwrap(), - stark_felt - ); - assert_eq!(versioned_state_proxys[2].get_class_hash_at(contract_address).unwrap(), class_hash); - assert_eq!( - versioned_state_proxys[5].get_compiled_class_hash(class_hash).unwrap(), - compiled_class_hash - ); - assert_eq!( - versioned_state_proxys[7].get_compiled_contract_class(class_hash).unwrap(), - contract_class - ); - - // Write to the state. - let new_key = storage_key!("0x11"); - let stark_felt_v3 = stark_felt!(14_u8); - let nonce_v4 = nonce!(3_u8); - let class_hash_v7 = class_hash!(28_u8); - let class_hash_v10 = class_hash!(29_u8); - let compiled_class_hash_v18 = compiled_class_hash!(30_u8); - let contract_class_v11 = FeatureContract::TestContract(CairoVersion::Cairo1).get_class(); - - versioned_state_proxys[3].state().apply_writes( - 3, - &StateMaps { - storage: HashMap::from([((contract_address, new_key), stark_felt_v3)]), - ..Default::default() - }, - &HashMap::default(), - ); - versioned_state_proxys[4].state().apply_writes( - 4, - &StateMaps { nonces: HashMap::from([(contract_address, nonce_v4)]), ..Default::default() }, - &HashMap::default(), - ); - versioned_state_proxys[7].state().apply_writes( - 7, - &StateMaps { - class_hashes: HashMap::from([(contract_address, class_hash_v7)]), - ..Default::default() - }, - &HashMap::default(), - ); - versioned_state_proxys[10].state().apply_writes( - 10, - &StateMaps { - class_hashes: HashMap::from([(contract_address, class_hash_v10)]), - ..Default::default() - }, - &HashMap::default(), - ); - versioned_state_proxys[18].state().apply_writes( - 18, - &StateMaps { - compiled_class_hashes: HashMap::from([(class_hash, compiled_class_hash_v18)]), - ..Default::default() - }, - &HashMap::default(), - ); - versioned_state_proxys[11].state().apply_writes( - 11, - &StateMaps::default(), - &HashMap::from([(class_hash, contract_class_v11.clone())]), - ); - - // Read the data - assert_eq!(versioned_state_proxys[2].get_nonce_at(contract_address).unwrap(), nonce); - assert_eq!(versioned_state_proxys[5].get_nonce_at(contract_address).unwrap(), nonce_v4); - assert_eq!( - versioned_state_proxys[5].get_storage_at(contract_address, key).unwrap(), - stark_felt - ); - assert_eq!( - versioned_state_proxys[5].get_storage_at(contract_address, new_key).unwrap(), - stark_felt_v3 - ); - assert_eq!(versioned_state_proxys[2].get_class_hash_at(contract_address).unwrap(), class_hash); - assert_eq!( - versioned_state_proxys[9].get_class_hash_at(contract_address).unwrap(), - class_hash_v7 - ); - // Ignore the writes in the current transaction. - assert_eq!( - versioned_state_proxys[10].get_class_hash_at(contract_address).unwrap(), - class_hash_v7 - ); - assert_eq!( - versioned_state_proxys[2].get_compiled_class_hash(class_hash).unwrap(), - compiled_class_hash - ); - assert_eq!( - versioned_state_proxys[19].get_compiled_class_hash(class_hash).unwrap(), - compiled_class_hash_v18 - ); - assert_eq!( - versioned_state_proxys[15].get_compiled_contract_class(class_hash).unwrap(), - contract_class_v11 - ); -} - -#[test] -// Test parallel execution of two transactions that use the same versioned state. -fn test_run_parallel_txs() { - let block_context = BlockContext::create_for_account_testing(); - let chain_info = &block_context.chain_info; - let zero_bounds = true; - - // Test Accounts - let grindy_account = FeatureContract::AccountWithLongValidate(CairoVersion::Cairo0); - let account_without_validation = - FeatureContract::AccountWithoutValidations(CairoVersion::Cairo0); - - // Initiate States - let versioned_state = Arc::new(Mutex::new(VersionedState::new(test_state( - chain_info, - BALANCE, - &[(account_without_validation, 1), (grindy_account, 1)], - )))); - - let safe_versioned_state = ThreadSafeVersionedState(Arc::clone(&versioned_state)); - let mut state_1 = CachedState::from(safe_versioned_state.pin_version(1)); - let mut state_2 = CachedState::from(safe_versioned_state.pin_version(2)); - - // Prepare transactions - let deploy_account_tx_1 = deploy_account_tx( - deploy_account_tx_args! { - class_hash: account_without_validation.get_class_hash(), - max_fee: Fee(u128::from(!zero_bounds)), - resource_bounds: l1_resource_bounds(u64::from(!zero_bounds), DEFAULT_STRK_L1_GAS_PRICE), - version: TransactionVersion::ONE, - }, - &mut NonceManager::default(), - ); - let account_tx_1 = AccountTransaction::DeployAccount(deploy_account_tx_1); - let enforce_fee = account_tx_1.create_tx_info().enforce_fee().unwrap(); - - let class_hash = grindy_account.get_class_hash(); - let ctor_storage_arg = stark_felt!(1_u8); - let ctor_grind_arg = stark_felt!(0_u8); // Do not grind in deploy phase. - let constructor_calldata = calldata![ctor_grind_arg, ctor_storage_arg]; - let deploy_tx_args = deploy_account_tx_args! { - class_hash, - max_fee: Fee(MAX_FEE), - constructor_calldata: constructor_calldata.clone(), - }; - let nonce_manager = &mut NonceManager::default(); - let deploy_account_tx_2 = deploy_account_tx(deploy_tx_args, nonce_manager); - let account_address = deploy_account_tx_2.contract_address; - let account_tx_2 = AccountTransaction::DeployAccount(deploy_account_tx_2); - - let deployed_account_balance_key = get_fee_token_var_address(account_address); - let fee_token_address = chain_info.fee_token_address(&FeeType::Eth); - state_2 - .set_storage_at(fee_token_address, deployed_account_balance_key, stark_felt!(BALANCE)) - .unwrap(); - - let block_context_1 = block_context.clone(); - let block_context_2 = block_context.clone(); - // Execute transactions - let thread_handle_1 = thread::spawn(move || { - let result = account_tx_1.execute(&mut state_1, &block_context_1, true, true); - assert_eq!(result.is_err(), enforce_fee); - }); - - let thread_handle_2 = thread::spawn(move || { - account_tx_2.execute(&mut state_2, &block_context_2, true, true).unwrap(); - - // Check that the constructor wrote ctor_arg to the storage. - let storage_key = get_storage_var_address("ctor_arg", &[]); - let deployed_contract_address = calculate_contract_address( - ContractAddressSalt::default(), - class_hash, - &constructor_calldata, - ContractAddress::default(), - ) - .unwrap(); - let read_storage_arg = - state_2.get_storage_at(deployed_contract_address, storage_key).unwrap(); - assert_eq!(ctor_storage_arg, read_storage_arg); - }); - - thread_handle_1.join().unwrap(); - thread_handle_2.join().unwrap(); -} diff --git a/crates/blockifier/src/concurrency/versioned_state_test.rs b/crates/blockifier/src/concurrency/versioned_state_test.rs new file mode 100644 index 00000000000..cc6c88b5eb1 --- /dev/null +++ b/crates/blockifier/src/concurrency/versioned_state_test.rs @@ -0,0 +1,491 @@ +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::thread; + +use rstest::{fixture, rstest}; +use starknet_api::core::{calculate_contract_address, ClassHash, ContractAddress, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::transaction::{Calldata, ContractAddressSalt, Fee, TransactionVersion}; +use starknet_api::{calldata, class_hash, contract_address, patricia_key, stark_felt}; + +use crate::abi::abi_utils::{get_fee_token_var_address, get_storage_var_address}; +use crate::concurrency::test_utils::{ + class_hash, + contract_address, + safe_versioned_state_for_testing, +}; +use crate::concurrency::versioned_state::{ + ThreadSafeVersionedState, + VersionedState, + VersionedStateProxy, +}; +use crate::concurrency::TxIndex; +use crate::context::BlockContext; +use crate::state::cached_state::{CachedState, ContractClassMapping, StateMaps}; +use crate::state::state_api::{State, StateReader, UpdatableState}; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::deploy_account::deploy_account_tx; +use crate::test_utils::dict_state_reader::DictStateReader; +use crate::test_utils::initial_test_state::test_state; +use crate::test_utils::{CairoVersion, NonceManager, BALANCE, DEFAULT_STRK_L1_GAS_PRICE, MAX_FEE}; +use crate::transaction::account_transaction::AccountTransaction; +use crate::transaction::objects::{FeeType, TransactionInfoCreator}; +use crate::transaction::test_utils::l1_resource_bounds; +use crate::transaction::transactions::ExecutableTransaction; +use crate::{compiled_class_hash, deploy_account_tx_args, nonce, storage_key}; + +#[fixture] +pub fn safe_versioned_state( + contract_address: ContractAddress, + class_hash: ClassHash, +) -> ThreadSafeVersionedState { + let init_state = DictStateReader { + address_to_class_hash: HashMap::from([(contract_address, class_hash)]), + ..Default::default() + }; + safe_versioned_state_for_testing(init_state) +} + +// TODO(OriF 15/5/24): Use `TransactionalState::create_transactional` instead of +// `CachedState::from(..)` when fits. +#[test] +fn test_versioned_state_proxy() { + // Test data + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); + let contract_address = contract_address!("0x1"); + let key = storage_key!("0x10"); + let stark_felt = stark_felt!(13_u8); + let nonce = nonce!(2_u8); + let class_hash = class_hash!(27_u8); + let compiled_class_hash = compiled_class_hash!(29_u8); + let contract_class = test_contract.get_class(); + + // Create the versioned state + let cached_state = CachedState::from(DictStateReader { + storage_view: HashMap::from([((contract_address, key), stark_felt)]), + address_to_nonce: HashMap::from([(contract_address, nonce)]), + address_to_class_hash: HashMap::from([(contract_address, class_hash)]), + class_hash_to_compiled_class_hash: HashMap::from([(class_hash, compiled_class_hash)]), + class_hash_to_class: HashMap::from([(class_hash, contract_class.clone())]), + }); + + let versioned_state = Arc::new(Mutex::new(VersionedState::new(cached_state))); + + let safe_versioned_state = ThreadSafeVersionedState(Arc::clone(&versioned_state)); + let versioned_state_proxys: Vec>> = + (0..20).map(|i| safe_versioned_state.pin_version(i)).collect(); + + // Read initial data + assert_eq!(versioned_state_proxys[5].get_nonce_at(contract_address).unwrap(), nonce); + assert_eq!(versioned_state_proxys[0].get_nonce_at(contract_address).unwrap(), nonce); + assert_eq!( + versioned_state_proxys[7].get_storage_at(contract_address, key).unwrap(), + stark_felt + ); + assert_eq!(versioned_state_proxys[2].get_class_hash_at(contract_address).unwrap(), class_hash); + assert_eq!( + versioned_state_proxys[5].get_compiled_class_hash(class_hash).unwrap(), + compiled_class_hash + ); + assert_eq!( + versioned_state_proxys[7].get_compiled_contract_class(class_hash).unwrap(), + contract_class + ); + + // Write to the state. + let new_key = storage_key!("0x11"); + let stark_felt_v3 = stark_felt!(14_u8); + let nonce_v4 = nonce!(3_u8); + let class_hash_v7 = class_hash!(28_u8); + let class_hash_v10 = class_hash!(29_u8); + let compiled_class_hash_v18 = compiled_class_hash!(30_u8); + let contract_class_v11 = FeatureContract::TestContract(CairoVersion::Cairo1).get_class(); + + versioned_state_proxys[3].state().apply_writes( + 3, + &StateMaps { + storage: HashMap::from([((contract_address, new_key), stark_felt_v3)]), + ..Default::default() + }, + &HashMap::default(), + ); + versioned_state_proxys[4].state().apply_writes( + 4, + &StateMaps { nonces: HashMap::from([(contract_address, nonce_v4)]), ..Default::default() }, + &HashMap::default(), + ); + versioned_state_proxys[7].state().apply_writes( + 7, + &StateMaps { + class_hashes: HashMap::from([(contract_address, class_hash_v7)]), + ..Default::default() + }, + &HashMap::default(), + ); + versioned_state_proxys[10].state().apply_writes( + 10, + &StateMaps { + class_hashes: HashMap::from([(contract_address, class_hash_v10)]), + ..Default::default() + }, + &HashMap::default(), + ); + versioned_state_proxys[18].state().apply_writes( + 18, + &StateMaps { + compiled_class_hashes: HashMap::from([(class_hash, compiled_class_hash_v18)]), + ..Default::default() + }, + &HashMap::default(), + ); + versioned_state_proxys[11].state().apply_writes( + 11, + &StateMaps::default(), + &HashMap::from([(class_hash, contract_class_v11.clone())]), + ); + + // Read the data + assert_eq!(versioned_state_proxys[2].get_nonce_at(contract_address).unwrap(), nonce); + assert_eq!(versioned_state_proxys[5].get_nonce_at(contract_address).unwrap(), nonce_v4); + assert_eq!( + versioned_state_proxys[5].get_storage_at(contract_address, key).unwrap(), + stark_felt + ); + assert_eq!( + versioned_state_proxys[5].get_storage_at(contract_address, new_key).unwrap(), + stark_felt_v3 + ); + assert_eq!(versioned_state_proxys[2].get_class_hash_at(contract_address).unwrap(), class_hash); + assert_eq!( + versioned_state_proxys[9].get_class_hash_at(contract_address).unwrap(), + class_hash_v7 + ); + // Ignore the writes in the current transaction. + assert_eq!( + versioned_state_proxys[10].get_class_hash_at(contract_address).unwrap(), + class_hash_v7 + ); + assert_eq!( + versioned_state_proxys[2].get_compiled_class_hash(class_hash).unwrap(), + compiled_class_hash + ); + assert_eq!( + versioned_state_proxys[19].get_compiled_class_hash(class_hash).unwrap(), + compiled_class_hash_v18 + ); + assert_eq!( + versioned_state_proxys[15].get_compiled_contract_class(class_hash).unwrap(), + contract_class_v11 + ); +} + +#[test] +// Test parallel execution of two transactions that use the same versioned state. +fn test_run_parallel_txs() { + let block_context = BlockContext::create_for_account_testing(); + let chain_info = &block_context.chain_info; + let zero_bounds = true; + + // Test Accounts + let grindy_account = FeatureContract::AccountWithLongValidate(CairoVersion::Cairo0); + let account_without_validation = + FeatureContract::AccountWithoutValidations(CairoVersion::Cairo0); + + // Initiate States + let versioned_state = Arc::new(Mutex::new(VersionedState::new(test_state( + chain_info, + BALANCE, + &[(account_without_validation, 1), (grindy_account, 1)], + )))); + + let safe_versioned_state = ThreadSafeVersionedState(Arc::clone(&versioned_state)); + let mut state_1 = CachedState::from(safe_versioned_state.pin_version(1)); + let mut state_2 = CachedState::from(safe_versioned_state.pin_version(2)); + + // Prepare transactions + let deploy_account_tx_1 = deploy_account_tx( + deploy_account_tx_args! { + class_hash: account_without_validation.get_class_hash(), + max_fee: Fee(u128::from(!zero_bounds)), + resource_bounds: l1_resource_bounds(u64::from(!zero_bounds), DEFAULT_STRK_L1_GAS_PRICE), + version: TransactionVersion::ONE, + }, + &mut NonceManager::default(), + ); + let account_tx_1 = AccountTransaction::DeployAccount(deploy_account_tx_1); + let enforce_fee = account_tx_1.create_tx_info().enforce_fee().unwrap(); + + let class_hash = grindy_account.get_class_hash(); + let ctor_storage_arg = stark_felt!(1_u8); + let ctor_grind_arg = stark_felt!(0_u8); // Do not grind in deploy phase. + let constructor_calldata = calldata![ctor_grind_arg, ctor_storage_arg]; + let deploy_tx_args = deploy_account_tx_args! { + class_hash, + max_fee: Fee(MAX_FEE), + constructor_calldata: constructor_calldata.clone(), + }; + let nonce_manager = &mut NonceManager::default(); + let deploy_account_tx_2 = deploy_account_tx(deploy_tx_args, nonce_manager); + let account_address = deploy_account_tx_2.contract_address; + let account_tx_2 = AccountTransaction::DeployAccount(deploy_account_tx_2); + + let deployed_account_balance_key = get_fee_token_var_address(account_address); + let fee_token_address = chain_info.fee_token_address(&FeeType::Eth); + state_2 + .set_storage_at(fee_token_address, deployed_account_balance_key, stark_felt!(BALANCE)) + .unwrap(); + + let block_context_1 = block_context.clone(); + let block_context_2 = block_context.clone(); + // Execute transactions + let thread_handle_1 = thread::spawn(move || { + let result = account_tx_1.execute(&mut state_1, &block_context_1, true, true); + assert_eq!(result.is_err(), enforce_fee); + }); + + let thread_handle_2 = thread::spawn(move || { + account_tx_2.execute(&mut state_2, &block_context_2, true, true).unwrap(); + + // Check that the constructor wrote ctor_arg to the storage. + let storage_key = get_storage_var_address("ctor_arg", &[]); + let deployed_contract_address = calculate_contract_address( + ContractAddressSalt::default(), + class_hash, + &constructor_calldata, + ContractAddress::default(), + ) + .unwrap(); + let read_storage_arg = + state_2.get_storage_at(deployed_contract_address, storage_key).unwrap(); + assert_eq!(ctor_storage_arg, read_storage_arg); + }); + + thread_handle_1.join().unwrap(); + thread_handle_2.join().unwrap(); +} + +#[rstest] +fn test_validate_read_set( + contract_address: ContractAddress, + class_hash: ClassHash, + safe_versioned_state: ThreadSafeVersionedState, +) { + let storage_key = storage_key!("0x10"); + + let transactional_state = CachedState::from(safe_versioned_state.pin_version(1)); + + // Validating tx index 0 always succeeds. + assert!(safe_versioned_state.pin_version(0).validate_reads(&StateMaps::default())); + + assert!(transactional_state.cache.borrow().initial_reads.storage.is_empty()); + transactional_state.get_storage_at(contract_address, storage_key).unwrap(); + assert_eq!(transactional_state.cache.borrow().initial_reads.storage.len(), 1); + + assert!(transactional_state.cache.borrow().initial_reads.nonces.is_empty()); + transactional_state.get_nonce_at(contract_address).unwrap(); + assert_eq!(transactional_state.cache.borrow().initial_reads.nonces.len(), 1); + + assert!(transactional_state.cache.borrow().initial_reads.class_hashes.is_empty()); + transactional_state.get_class_hash_at(contract_address).unwrap(); + assert_eq!(transactional_state.cache.borrow().initial_reads.class_hashes.len(), 1); + + assert!(transactional_state.cache.borrow().initial_reads.compiled_class_hashes.is_empty()); + transactional_state.get_compiled_class_hash(class_hash).unwrap(); + assert_eq!(transactional_state.cache.borrow().initial_reads.compiled_class_hashes.len(), 1); + + // TODO(OriF 15/5/24): add a check for `get_compiled_contract_class`` once the deploy account + // preceding a declare flow is solved. + + assert!( + safe_versioned_state + .pin_version(1) + .validate_reads(&transactional_state.cache.borrow().initial_reads) + ); +} + +#[rstest] +fn test_apply_writes( + contract_address: ContractAddress, + class_hash: ClassHash, + safe_versioned_state: ThreadSafeVersionedState, +) { + let mut transactional_states: Vec>> = + (0..2).map(|i| CachedState::from(safe_versioned_state.pin_version(i))).collect(); + + // Transaction 0 class hash. + let class_hash_0 = class_hash!(76_u8); + assert!(transactional_states[0].cache.borrow().writes.class_hashes.is_empty()); + transactional_states[0].set_class_hash_at(contract_address, class_hash_0).unwrap(); + assert_eq!(transactional_states[0].cache.borrow().writes.class_hashes.len(), 1); + + // Transaction 0 contract class. + let contract_class_0 = FeatureContract::TestContract(CairoVersion::Cairo1).get_class(); + assert!(transactional_states[0].class_hash_to_class.borrow().is_empty()); + transactional_states[0].set_contract_class(class_hash, contract_class_0.clone()).unwrap(); + assert_eq!(transactional_states[0].class_hash_to_class.borrow().len(), 1); + + safe_versioned_state.pin_version(0).apply_writes( + &transactional_states[0].cache.borrow().writes, + &transactional_states[0].class_hash_to_class.borrow().clone(), + &HashMap::default(), + ); + assert!(transactional_states[1].get_class_hash_at(contract_address).unwrap() == class_hash_0); + assert!( + transactional_states[1].get_compiled_contract_class(class_hash).unwrap() + == contract_class_0 + ); +} + +#[rstest] +fn test_apply_writes_reexecute_scenario( + contract_address: ContractAddress, + class_hash: ClassHash, + safe_versioned_state: ThreadSafeVersionedState, +) { + let mut transactional_states: Vec>> = + (0..2).map(|i| CachedState::from(safe_versioned_state.pin_version(i))).collect(); + + // Transaction 0 class hash. + let class_hash_0 = class_hash!(76_u8); + transactional_states[0].set_class_hash_at(contract_address, class_hash_0).unwrap(); + + // As transaction 0 hasn't written to the shared state yet, the class hash should not be + // updated. + assert!(transactional_states[1].get_class_hash_at(contract_address).unwrap() == class_hash); + + safe_versioned_state.pin_version(0).apply_writes( + &transactional_states[0].cache.borrow().writes, + &transactional_states[0].class_hash_to_class.borrow().clone(), + &HashMap::default(), + ); + // Although transaction 0 wrote to the shared state, version 1 needs to be re-executed to see + // the new value (its read value has already been cached). + assert!(transactional_states[1].get_class_hash_at(contract_address).unwrap() == class_hash); + + // TODO: Use re-execution native util once it's ready. + // "Re-execute" the transaction. + transactional_states[1] = CachedState::from(safe_versioned_state.pin_version(1)); + // The class hash should be updated. + assert!(transactional_states[1].get_class_hash_at(contract_address).unwrap() == class_hash_0); +} + +#[rstest] +fn test_delete_writes( + #[values(0, 1, 2)] tx_index_to_delete_writes: TxIndex, + safe_versioned_state: ThreadSafeVersionedState, +) { + let num_of_txs = 3; + let mut transactional_states: Vec>> = + (0..num_of_txs).map(|i| CachedState::from(safe_versioned_state.pin_version(i))).collect(); + // Setting 2 instances of the contract to ensure `delete_writes` removes information from + // multiple keys. Class hash values are not checked in this test. + let contract_addresses = [ + (contract_address!("0x100"), class_hash!(20_u8)), + (contract_address!("0x200"), class_hash!(21_u8)), + ]; + let feature_contract = FeatureContract::TestContract(CairoVersion::Cairo1); + for tx_state in transactional_states.iter_mut() { + // Modify the `cache` member of the CachedState. + for (contract_address, class_hash) in contract_addresses.iter() { + tx_state.set_class_hash_at(*contract_address, *class_hash).unwrap(); + } + // Modify the `class_hash_to_class` member of the CachedState. + tx_state + .set_contract_class(feature_contract.get_class_hash(), feature_contract.get_class()) + .unwrap(); + tx_state.state.apply_writes( + &tx_state.cache.borrow().writes, + &tx_state.class_hash_to_class.borrow(), + &HashMap::default(), + ); + } + + transactional_states[tx_index_to_delete_writes].state.delete_writes( + &transactional_states[tx_index_to_delete_writes].cache.borrow().writes, + &transactional_states[tx_index_to_delete_writes].class_hash_to_class.borrow(), + ); + + for tx_index in 0..num_of_txs { + let should_be_empty = tx_index == tx_index_to_delete_writes; + assert_eq!( + safe_versioned_state + .0 + .lock() + .unwrap() + .get_writes_of_index(tx_index) + .class_hashes + .is_empty(), + should_be_empty + ); + + assert_eq!( + safe_versioned_state + .0 + .lock() + .unwrap() + .compiled_contract_classes + .get_writes_of_index(tx_index) + .is_empty(), + should_be_empty + ); + } +} + +#[rstest] +fn test_delete_writes_completeness( + safe_versioned_state: ThreadSafeVersionedState, +) { + let state_maps_writes = StateMaps { + nonces: HashMap::from([(contract_address!("0x1"), nonce!("0x1"))]), + class_hashes: HashMap::from([(contract_address!("0x1"), class_hash!("0x1"))]), + storage: HashMap::from([( + (contract_address!("0x1"), storage_key!("0x1")), + stark_felt!("0x1"), + )]), + compiled_class_hashes: HashMap::from([(class_hash!("0x1"), compiled_class_hash!("0x1"))]), + // TODO (OriF, 01/07/2024): Uncomment the following line and remove the line below it once + // `declared_contracts` mapping logic in StateMaps is complete. + // declared_contracts: HashMap::from([(class_hash!("0x1"), true)]), + declared_contracts: HashMap::default(), + }; + let feature_contract = FeatureContract::TestContract(CairoVersion::Cairo1); + let class_hash_to_class_writes = + HashMap::from([(feature_contract.get_class_hash(), feature_contract.get_class())]); + + let tx_index = 0; + let mut versioned_state_proxy = safe_versioned_state.pin_version(tx_index); + + versioned_state_proxy.apply_writes( + &state_maps_writes, + &class_hash_to_class_writes, + &HashMap::default(), + ); + assert_eq!( + safe_versioned_state.0.lock().unwrap().get_writes_of_index(tx_index), + state_maps_writes + ); + assert_eq!( + safe_versioned_state + .0 + .lock() + .unwrap() + .compiled_contract_classes + .get_writes_of_index(tx_index), + class_hash_to_class_writes + ); + + versioned_state_proxy.delete_writes(&state_maps_writes, &class_hash_to_class_writes); + assert_eq!( + safe_versioned_state.0.lock().unwrap().get_writes_of_index(tx_index), + StateMaps::default() + ); + assert_eq!( + safe_versioned_state + .0 + .lock() + .unwrap() + .compiled_contract_classes + .get_writes_of_index(tx_index), + ContractClassMapping::default() + ); +} diff --git a/crates/blockifier/src/concurrency/versioned_storage.rs b/crates/blockifier/src/concurrency/versioned_storage.rs index 61379dc79c3..dba52e54c21 100644 --- a/crates/blockifier/src/concurrency/versioned_storage.rs +++ b/crates/blockifier/src/concurrency/versioned_storage.rs @@ -12,6 +12,7 @@ pub mod test; /// It is versioned in the sense that it holds a state of write operations done on it by /// different versions of executions. /// This allows maintaining the cells with the correct values in the context of each execution. +#[derive(Debug)] pub struct VersionedStorage where K: Clone + Copy + Eq + Hash + Debug, @@ -50,6 +51,16 @@ where cell.insert(tx_index, value); } + pub fn delete_write(&mut self, key: K, tx_index: TxIndex) { + self.writes + .get_mut(&key) + .expect( + "A 'delete_write' call must be preceded by a 'write' call with the corresponding \ + key", + ) + .remove(&tx_index); + } + /// This method inserts the provided key-value pair into the cached initial values map. /// It is typically used when reading a value that is not found in the versioned storage. In /// such a scenario, the value is retrieved from the initial storage and written to the @@ -58,13 +69,24 @@ where self.cached_initial_values.insert(key, value); } - pub(crate) fn get_writes_from_index(&self, from_index: TxIndex) -> HashMap { + pub(crate) fn get_writes_up_to_index(&self, index: TxIndex) -> HashMap { let mut writes = HashMap::default(); for (&key, cell) in self.writes.iter() { - if let Some(value) = cell.range(..=from_index).next_back() { + if let Some(value) = cell.range(..=index).next_back() { writes.insert(key, value.1.clone()); } } writes } + + #[cfg(any(feature = "testing", test))] + pub fn get_writes_of_index(&self, tx_index: TxIndex) -> HashMap { + let mut writes = HashMap::default(); + for (&key, cell) in self.writes.iter() { + if let Some(value) = cell.get(&tx_index) { + writes.insert(key, value.clone()); + } + } + writes + } } diff --git a/crates/blockifier/src/concurrency/versioned_storage_test.rs b/crates/blockifier/src/concurrency/versioned_storage_test.rs index 2ee7a950cec..34b63490731 100644 --- a/crates/blockifier/src/concurrency/versioned_storage_test.rs +++ b/crates/blockifier/src/concurrency/versioned_storage_test.rs @@ -1,7 +1,14 @@ +use std::collections::{BTreeMap, HashMap}; + use pretty_assertions::assert_eq; +use rstest::rstest; +use starknet_api::core::{ClassHash, ContractAddress}; +use crate::concurrency::test_utils::{class_hash, contract_address}; use crate::concurrency::versioned_storage::VersionedStorage; +use crate::concurrency::TxIndex; +// TODO(barak, 01/07/2024): Split into test_read() and test_write(). #[test] fn test_versioned_storage() { let mut storage = VersionedStorage::default(); @@ -37,3 +44,29 @@ fn test_versioned_storage() { // Test the write. assert_eq!(storage.read(50, 100).unwrap(), 194); } + +#[rstest] +fn test_delete_write( + contract_address: ContractAddress, + class_hash: ClassHash, + #[values(0, 1, 2)] tx_index_to_delete_writes: TxIndex, +) { + // TODO(barak, 01/07/2025): Create a macro versioned_storage!. + let num_of_txs = 3; + let mut versioned_storage = VersionedStorage { + cached_initial_values: HashMap::default(), + writes: HashMap::from([( + contract_address, + // Class hash values are not checked in this test. + BTreeMap::from_iter((0..num_of_txs).map(|i| (i, class_hash))), + )]), + }; + for tx_index in 0..num_of_txs { + let should_contain_tx_index_writes = tx_index != tx_index_to_delete_writes; + versioned_storage.delete_write(contract_address, tx_index_to_delete_writes); + assert_eq!( + versioned_storage.writes.get(&contract_address).unwrap().contains_key(&tx_index), + should_contain_tx_index_writes + ) + } +} diff --git a/crates/blockifier/src/concurrency/worker_logic.rs b/crates/blockifier/src/concurrency/worker_logic.rs new file mode 100644 index 00000000000..daa897ef1cc --- /dev/null +++ b/crates/blockifier/src/concurrency/worker_logic.rs @@ -0,0 +1,262 @@ +use std::collections::{HashMap, HashSet}; +use std::fmt::Debug; +use std::sync::{Arc, Mutex}; + +use cairo_felt::Felt252; +use num_traits::Bounded; +use starknet_api::core::{ClassHash, ContractAddress}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::Fee; + +use super::versioned_state::VersionedStateProxy; +use crate::concurrency::fee_utils::fill_sequencer_balance_reads; +use crate::concurrency::scheduler::{Scheduler, Task}; +use crate::concurrency::utils::lock_mutex_in_array; +use crate::concurrency::versioned_state::ThreadSafeVersionedState; +use crate::concurrency::TxIndex; +use crate::context::BlockContext; +use crate::execution::execution_utils::{felt_to_stark_felt, stark_felt_to_felt}; +use crate::fee::fee_utils::get_sequencer_balance_keys; +use crate::state::cached_state::{ContractClassMapping, StateMaps, TransactionalState}; +use crate::state::state_api::{StateReader, StateResult, UpdatableState}; +use crate::transaction::objects::{TransactionExecutionInfo, TransactionExecutionResult}; +use crate::transaction::transaction_execution::Transaction; +use crate::transaction::transactions::ExecutableTransaction; + +const EXECUTION_OUTPUTS_UNWRAP_ERROR: &str = "Execution task outputs should not be None."; + +#[cfg(test)] +#[path = "worker_logic_test.rs"] +pub mod test; + +#[derive(Debug)] +pub struct ExecutionTaskOutput { + pub reads: StateMaps, + pub writes: StateMaps, + pub contract_classes: ContractClassMapping, + pub visited_pcs: HashMap>, + pub result: TransactionExecutionResult, +} + +pub struct WorkerExecutor<'a, S: StateReader> { + pub scheduler: Scheduler, + pub state: ThreadSafeVersionedState, + pub chunk: &'a [Transaction], + pub execution_outputs: Box<[Mutex>]>, + pub block_context: BlockContext, +} +impl<'a, S: StateReader> WorkerExecutor<'a, S> { + pub fn new( + state: ThreadSafeVersionedState, + chunk: &'a [Transaction], + block_context: BlockContext, + ) -> Self { + let scheduler = Scheduler::new(chunk.len()); + let execution_outputs = + std::iter::repeat_with(|| Mutex::new(None)).take(chunk.len()).collect(); + + WorkerExecutor { scheduler, state, chunk, execution_outputs, block_context } + } + + pub fn run(&self) -> StateResult<()> { + let mut task = Task::NoTask; + loop { + self.commit_while_possible()?; + task = match task { + Task::ExecutionTask(tx_index) => { + self.execute(tx_index); + Task::NoTask + } + Task::ValidationTask(tx_index) => self.validate(tx_index), + Task::NoTask => self.scheduler.next_task(), + Task::Done => break, + }; + } + Ok(()) + } + + fn commit_while_possible(&self) -> StateResult<()> { + if let Some(mut transaction_committer) = self.scheduler.try_enter_commit_phase() { + while let Some(tx_index) = transaction_committer.try_commit() { + let commit_succeeded = self.commit_tx(tx_index)?; + if !commit_succeeded { + transaction_committer.halt_scheduler(); + } + } + } + Ok(()) + } + + fn execute(&self, tx_index: TxIndex) { + self.execute_tx(tx_index); + self.scheduler.finish_execution(tx_index) + } + + fn execute_tx(&self, tx_index: TxIndex) { + let mut tx_versioned_state = self.state.pin_version(tx_index); + let tx = &self.chunk[tx_index]; + let mut transactional_state = + TransactionalState::create_transactional(&mut tx_versioned_state); + let validate = true; + let charge_fee = true; + + let execution_result = + tx.execute_raw(&mut transactional_state, &self.block_context, charge_fee, validate); + + if execution_result.is_ok() { + // TODO(Noa, 15/05/2024): use `tx_versioned_state` when we add support to transactional + // versioned state. + self.state.pin_version(tx_index).apply_writes( + &transactional_state.cache.borrow().writes, + &transactional_state.class_hash_to_class.borrow(), + &HashMap::default(), + ); + } + + // Write the transaction execution outputs. + let tx_reads_writes = transactional_state.cache.take(); + let class_hash_to_class = transactional_state.class_hash_to_class.take(); + // In case of a failed transaction, we don't record its writes and visited pcs. + let (writes, contract_classes, visited_pcs) = match execution_result { + Ok(_) => (tx_reads_writes.writes, class_hash_to_class, transactional_state.visited_pcs), + Err(_) => (StateMaps::default(), HashMap::default(), HashMap::default()), + }; + let mut execution_output = lock_mutex_in_array(&self.execution_outputs, tx_index); + *execution_output = Some(ExecutionTaskOutput { + reads: tx_reads_writes.initial_reads, + writes, + contract_classes, + visited_pcs, + result: execution_result, + }); + } + + fn validate(&self, tx_index: TxIndex) -> Task { + let tx_versioned_state = self.state.pin_version(tx_index); + let execution_output = lock_mutex_in_array(&self.execution_outputs, tx_index); + let execution_output = execution_output.as_ref().expect(EXECUTION_OUTPUTS_UNWRAP_ERROR); + let reads = &execution_output.reads; + let reads_valid = tx_versioned_state.validate_reads(reads); + + let aborted = !reads_valid && self.scheduler.try_validation_abort(tx_index); + if aborted { + tx_versioned_state + .delete_writes(&execution_output.writes, &execution_output.contract_classes); + self.scheduler.finish_abort(tx_index) + } else { + Task::NoTask + } + } + + /// Commits a transaction. The commit process is as follows: + /// 1) Validate the read set. + /// * If validation failed, delete the transaction writes and (re-)execute it. + /// * Else (validation succeeded), no need to re-execute. + /// 2) Execution is final. + /// * If execution succeeded, ask the bouncer if there is room for the transaction in the + /// block. + /// - If there is room, fix the call info, update the sequencer balance and commit the + /// transaction. + /// - Else (no room), do not commit. The block should be closed without the transaction. + /// * Else (execution failed), commit the transaction without fixing the call info or + /// updating the sequencer balance. + // TODO(Meshi, 01/06/2024): Remove dead code. + #[allow(dead_code)] + fn commit_tx(&self, tx_index: TxIndex) -> StateResult { + let execution_output = lock_mutex_in_array(&self.execution_outputs, tx_index); + + let tx = &self.chunk[tx_index]; + let mut tx_versioned_state = self.state.pin_version(tx_index); + + let reads = &execution_output.as_ref().expect(EXECUTION_OUTPUTS_UNWRAP_ERROR).reads; + let reads_valid = tx_versioned_state.validate_reads(reads); + drop(execution_output); + + // First, re-validate the transaction. + if !reads_valid { + // Revalidate failed: re-execute the transaction, and commit. + // TODO(Meshi, 01/06/2024): Delete the transaction writes. + self.execute_tx(tx_index); + let execution_output = lock_mutex_in_array(&self.execution_outputs, tx_index); + let read_set = &execution_output.as_ref().expect(EXECUTION_OUTPUTS_UNWRAP_ERROR).reads; + // Another validation after the re-execution for sanity check. + assert!(tx_versioned_state.validate_reads(read_set)); + } + + // Execution is final. + let mut execution_output = lock_mutex_in_array(&self.execution_outputs, tx_index); + let result_tx_info = + &mut execution_output.as_mut().expect(EXECUTION_OUTPUTS_UNWRAP_ERROR).result; + + let tx_context = Arc::new(self.block_context.to_tx_context(tx)); + // Fix the sequencer balance. + // There is no need to fix the balance when the sequencer transfers fee to itself, since we + // use the sequential fee transfer in this case. + if let Ok(tx_info) = result_tx_info.as_mut() { + // TODO(Meshi, 01/06/2024): check if this is also needed in the bouncer. + if tx_context.tx_info.sender_address() + != self.block_context.block_info.sequencer_address + { + // Update the sequencer balance in the storage. + let mut next_tx_versioned_state = self.state.pin_version(tx_index + 1); + let (sequencer_balance_value_low, sequencer_balance_value_high) = + next_tx_versioned_state.get_fee_token_balance( + tx_context.block_context.block_info.sequencer_address, + tx_context.fee_token_address(), + )?; + if let Some(fee_transfer_call_info) = tx_info.fee_transfer_call_info.as_mut() { + // Fix the transfer call info. + fill_sequencer_balance_reads( + fee_transfer_call_info, + sequencer_balance_value_low, + sequencer_balance_value_high, + ); + } + add_fee_to_sequencer_balance( + tx_context.fee_token_address(), + &mut tx_versioned_state, + tx_info.actual_fee, + &self.block_context, + sequencer_balance_value_low, + sequencer_balance_value_high, + ); + } + } + + Ok(true) + } +} + +// Utilities. + +// TODO(Meshi, 01/06/2024): Remove dead code. +#[allow(dead_code)] +fn add_fee_to_sequencer_balance( + fee_token_address: ContractAddress, + tx_versioned_state: &mut VersionedStateProxy, + actual_fee: Fee, + block_context: &BlockContext, + sequencer_balance_value_low: StarkFelt, + sequencer_balance_value_high: StarkFelt, +) { + let (sequencer_balance_key_low, sequencer_balance_key_high) = + get_sequencer_balance_keys(block_context); + let felt_fee = &Felt252::from(actual_fee.0); + let new_value_low = stark_felt_to_felt(sequencer_balance_value_low) + felt_fee; + let overflow = + stark_felt_to_felt(sequencer_balance_value_low) > Felt252::max_value() - felt_fee; + let new_value_high = if overflow { + stark_felt_to_felt(sequencer_balance_value_high) + Felt252::from(1_u8) + } else { + stark_felt_to_felt(sequencer_balance_value_high) + }; + + let writes = StateMaps { + storage: HashMap::from([ + ((fee_token_address, sequencer_balance_key_low), felt_to_stark_felt(&new_value_low)), + ((fee_token_address, sequencer_balance_key_high), felt_to_stark_felt(&new_value_high)), + ]), + ..StateMaps::default() + }; + tx_versioned_state.apply_writes(&writes, &ContractClassMapping::default(), &HashMap::default()); +} diff --git a/crates/blockifier/src/concurrency/worker_logic_test.rs b/crates/blockifier/src/concurrency/worker_logic_test.rs new file mode 100644 index 00000000000..cb1f1e5ee8c --- /dev/null +++ b/crates/blockifier/src/concurrency/worker_logic_test.rs @@ -0,0 +1,302 @@ +use std::collections::HashMap; + +use starknet_api::core::{ContractAddress, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::transaction::Fee; +use starknet_api::{contract_address, patricia_key, stark_felt}; + +use super::WorkerExecutor; +use crate::abi::abi_utils::get_fee_token_var_address; +use crate::abi::sierra_types::next_storage_key; +use crate::concurrency::scheduler::{Task, TransactionStatus}; +use crate::concurrency::test_utils::safe_versioned_state_for_testing; +use crate::context::BlockContext; +use crate::state::cached_state::StateMaps; +use crate::state::state_api::StateReader; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::initial_test_state::test_state_reader; +use crate::test_utils::{ + create_calldata, + CairoVersion, + NonceManager, + BALANCE, + MAX_FEE, + TEST_ERC20_CONTRACT_ADDRESS, +}; +use crate::transaction::test_utils::account_invoke_tx; +use crate::transaction::transaction_execution::Transaction; +use crate::{invoke_tx_args, nonce, storage_key}; + +#[test] +fn test_worker_execute() { + // Settings. + let concurrency_mode = true; + let block_context = + BlockContext::create_for_account_testing_with_concurrency_mode(concurrency_mode); + let account_contract = FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1); + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); + let chain_info = &block_context.chain_info; + + // Create the state. + let state_reader = + test_state_reader(chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); + let safe_versioned_state = safe_versioned_state_for_testing(state_reader); + + // Create transactions. + let test_contract_address = test_contract.get_instance_address(0); + let account_address = account_contract.get_instance_address(0); + let nonce_manager = &mut NonceManager::default(); + let storage_value = stark_felt!(93_u8); + let storage_key = storage_key!(1993_u16); + + let tx_success = account_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata: create_calldata( + test_contract_address, + "test_storage_read_write", + &[*storage_key.0.key(),storage_value ], // Calldata: address, value. + ), + max_fee: Fee(MAX_FEE), + nonce: nonce_manager.next(account_address) + }); + + // Create a transaction with invalid nonce. + nonce_manager.rollback(account_address); + let tx_failure = account_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata: create_calldata( + test_contract_address, + "test_storage_read_write", + &[*storage_key.0.key(),storage_value ], // Calldata: address, value. + ), + max_fee: Fee(MAX_FEE), + nonce: nonce_manager.next(account_address) + + }); + + let tx_revert = account_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata: create_calldata( + test_contract_address, + "write_and_revert", + &[stark_felt!(1991_u16),storage_value ], // Calldata: address, value. + ), + max_fee: Fee(MAX_FEE), + nonce: nonce_manager.next(account_address) + + }); + + // Concurrency settings. + let txs = [tx_success, tx_failure, tx_revert] + .into_iter() + .map(Transaction::AccountTransaction) + .collect::>(); + + let worker_executor = WorkerExecutor::new(safe_versioned_state.clone(), &txs, block_context); + + // Creates 3 execution active tasks. + worker_executor.scheduler.next_task(); + worker_executor.scheduler.next_task(); + worker_executor.scheduler.next_task(); + + // Successful execution. + let tx_index = 0; + worker_executor.execute(tx_index); + // Read a write made by the transaction. + assert_eq!( + safe_versioned_state + .pin_version(tx_index + 1) + .get_storage_at(test_contract_address, storage_key) + .unwrap(), + storage_value + ); + // Verify the output was written. Validate its correctness. + let execution_output = worker_executor.execution_outputs[tx_index].lock().unwrap(); + let execution_output = execution_output.as_ref().unwrap(); + let result = execution_output.result.as_ref().unwrap(); + let account_balance = BALANCE - result.actual_fee.0; + assert!(!result.is_reverted()); + + let erc20 = FeatureContract::ERC20; + let erc_contract_address = contract_address!(TEST_ERC20_CONTRACT_ADDRESS); + let account_balance_key_low = get_fee_token_var_address(account_address); + let account_balance_key_high = next_storage_key(&account_balance_key_low).unwrap(); + // Both in write and read sets, only the account balance appear, and not the sequencer balance. + // This is because when executing transaction in concurrency mode on, we manually remove the + // writes and reads to and from the sequencer balance (to avoid the inevitable dependency + // between all the transactions). + let writes = StateMaps { + nonces: HashMap::from([(account_address, nonce!(1_u8))]), + storage: HashMap::from([ + ((test_contract_address, storage_key), storage_value), + ((erc_contract_address, account_balance_key_low), stark_felt!(account_balance)), + ((erc_contract_address, account_balance_key_high), stark_felt!(0_u8)), + ]), + ..Default::default() + }; + let reads = StateMaps { + nonces: HashMap::from([(account_address, nonce!(0_u8))]), + // Before running an entry point (call contract), we verify the contract is deployed. + class_hashes: HashMap::from([ + (account_address, account_contract.get_class_hash()), + (test_contract_address, test_contract.get_class_hash()), + (erc_contract_address, erc20.get_class_hash()), + ]), + storage: HashMap::from([ + ((test_contract_address, storage_key), stark_felt!(0_u8)), + ((erc_contract_address, account_balance_key_low), stark_felt!(BALANCE)), + ((erc_contract_address, account_balance_key_high), stark_felt!(0_u8)), + ]), + // When running an entry point, we load its contract class. + declared_contracts: HashMap::from([ + (account_contract.get_class_hash(), true), + (test_contract.get_class_hash(), true), + (erc20.get_class_hash(), true), + ]), + ..Default::default() + }; + + assert_eq!(execution_output.writes, writes); + assert_eq!(execution_output.reads, reads); + assert_ne!(execution_output.visited_pcs, HashMap::default()); + + // Failed execution. + let tx_index = 1; + worker_executor.execute(tx_index); + // No write was made by the transaction. + assert_eq!( + safe_versioned_state.pin_version(tx_index + 1).get_nonce_at(account_address).unwrap(), + nonce!(1_u8) + ); + let execution_output = worker_executor.execution_outputs[tx_index].lock().unwrap(); + let execution_output = execution_output.as_ref().unwrap(); + assert!(execution_output.result.as_ref().is_err()); + let reads = StateMaps { + nonces: HashMap::from([(account_address, nonce!(1_u8))]), + ..Default::default() + }; + assert_eq!(execution_output.reads, reads); + assert_eq!(execution_output.writes, StateMaps::default()); + assert_eq!(execution_output.visited_pcs, HashMap::default()); + + // Reverted execution. + let tx_index = 2; + worker_executor.execute(tx_index); + // Read a write made by the transaction. + assert_eq!( + safe_versioned_state.pin_version(tx_index + 1).get_nonce_at(account_address).unwrap(), + nonce!(2_u8) + ); + let execution_output = worker_executor.execution_outputs[tx_index].lock().unwrap(); + let execution_output = execution_output.as_ref().unwrap(); + assert!(execution_output.result.as_ref().unwrap().is_reverted()); + assert_ne!(execution_output.writes, StateMaps::default()); + assert_ne!(execution_output.visited_pcs, HashMap::default()); + + // Validate status change. + for tx_index in 0..3 { + assert_eq!(*worker_executor.scheduler.get_tx_status(tx_index), TransactionStatus::Executed); + } +} + +#[test] +fn test_worker_validate() { + // Settings. + let concurrency_mode = true; + let block_context = + BlockContext::create_for_account_testing_with_concurrency_mode(concurrency_mode); + + let account_contract = FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1); + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); + let chain_info = &block_context.chain_info; + + // Create the state. + let state_reader = + test_state_reader(chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); + let safe_versioned_state = safe_versioned_state_for_testing(state_reader); + + // Create transactions. + let test_contract_address = test_contract.get_instance_address(0); + let account_address = account_contract.get_instance_address(0); + let nonce_manager = &mut NonceManager::default(); + let storage_value0 = stark_felt!(93_u8); + let storage_value1 = stark_felt!(39_u8); + let storage_key = storage_key!(1993_u16); + + // Both transactions change the same storage key. + let account_tx0 = account_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata: create_calldata( + test_contract_address, + "test_storage_read_write", + &[*storage_key.0.key(),storage_value0 ], // Calldata: address, value. + ), + max_fee: Fee(MAX_FEE), + nonce: nonce_manager.next(account_address) + }); + + let account_tx1 = account_invoke_tx(invoke_tx_args! { + sender_address: account_address, + calldata: create_calldata( + test_contract_address, + "test_storage_read_write", + &[*storage_key.0.key(),storage_value1 ], // Calldata: address, value. + ), + max_fee: Fee(MAX_FEE), + nonce: nonce_manager.next(account_address) + + }); + + // Concurrency settings. + let txs = [account_tx0, account_tx1] + .into_iter() + .map(Transaction::AccountTransaction) + .collect::>(); + + let worker_executor = WorkerExecutor::new(safe_versioned_state.clone(), &txs, block_context); + + // Creates 2 active tasks. + worker_executor.scheduler.next_task(); + worker_executor.scheduler.next_task(); + + // Execute transactions in the wrong order, making the first execution invalid. + worker_executor.execute(1); + worker_executor.execute(0); + + // Creates 2 active tasks. + worker_executor.scheduler.next_task(); + worker_executor.scheduler.next_task(); + + // Validate succeeds. + let tx_index = 0; + let next_task = worker_executor.validate(tx_index); + assert_eq!(next_task, Task::NoTask); + // Verify writes exist in state. + assert_eq!( + safe_versioned_state + .pin_version(tx_index + 1) + .get_storage_at(test_contract_address, storage_key) + .unwrap(), + storage_value0 + ); + // No status change. + assert_eq!(*worker_executor.scheduler.get_tx_status(tx_index), TransactionStatus::Executed); + + // Validate failed. Invoke 2 failed validations; only the first leads to a re-execution. + let tx_index = 1; + let next_task1 = worker_executor.validate(tx_index); + assert_eq!(next_task1, Task::ExecutionTask(tx_index)); + // Verify writes were removed. + assert_eq!( + safe_versioned_state + .pin_version(tx_index + 1) + .get_storage_at(test_contract_address, storage_key) + .unwrap(), + storage_value0 + ); + // Verify status change. + assert_eq!(*worker_executor.scheduler.get_tx_status(tx_index), TransactionStatus::Executing); + + let next_task2 = worker_executor.validate(tx_index); + assert_eq!(next_task2, Task::NoTask); +} diff --git a/crates/blockifier/src/execution.rs b/crates/blockifier/src/execution.rs index 2ee1dde5b3f..6636d98efff 100644 --- a/crates/blockifier/src/execution.rs +++ b/crates/blockifier/src/execution.rs @@ -9,4 +9,5 @@ pub mod entry_point_execution; pub mod errors; pub mod execution_utils; pub mod hint_code; +pub mod stack_trace; pub mod syscalls; diff --git a/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs b/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs index d90f23e322d..9f50a6333e7 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/deprecated_syscalls_test.rs @@ -161,7 +161,7 @@ fn test_nested_library_call() { call: nested_storage_entry_point, execution: CallExecution::from_retdata(retdata![stark_felt!(value + 1)]), resources: storage_entry_point_resources.clone(), - storage_read_values: vec![stark_felt!(0_u8), stark_felt!(value + 1)], + storage_read_values: vec![stark_felt!(value + 1)], accessed_storage_keys: HashSet::from([storage_key!(key + 1)]), ..Default::default() }; @@ -183,7 +183,7 @@ fn test_nested_library_call() { call: storage_entry_point, execution: CallExecution::from_retdata(retdata![stark_felt!(value)]), resources: storage_entry_point_resources.clone(), - storage_read_values: vec![stark_felt!(0_u8), stark_felt!(value)], + storage_read_values: vec![stark_felt!(value)], accessed_storage_keys: HashSet::from([storage_key!(key)]), ..Default::default() }; @@ -248,7 +248,7 @@ fn test_call_contract() { n_memory_holes: 0, builtin_instance_counter: HashMap::from([(RANGE_CHECK_BUILTIN_NAME.to_string(), 2)]), }, - storage_read_values: vec![StarkFelt::ZERO, stark_felt!(value)], + storage_read_values: vec![stark_felt!(value)], accessed_storage_keys: HashSet::from([storage_key!(key)]), ..Default::default() }; diff --git a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs index fd2d3f05609..1810a4584c1 100644 --- a/crates/blockifier/src/execution/deprecated_syscalls/mod.rs +++ b/crates/blockifier/src/execution/deprecated_syscalls/mod.rs @@ -763,9 +763,5 @@ pub fn storage_write( _vm: &mut VirtualMachine, syscall_handler: &mut DeprecatedSyscallHintProcessor<'_>, ) -> DeprecatedSyscallResult { - // Read the value before the write operation in order to log it in the list of read· - // values. This is needed to correctly build the `DictAccess` entry corresponding to· - // `storage_write` syscall in the OS. - syscall_handler.get_contract_storage_at(request.address)?; syscall_handler.set_contract_storage_at(request.address, request.value) } diff --git a/crates/blockifier/src/execution/entry_point_test.rs b/crates/blockifier/src/execution/entry_point_test.rs index c6722051477..353e3ff936a 100644 --- a/crates/blockifier/src/execution/entry_point_test.rs +++ b/crates/blockifier/src/execution/entry_point_test.rs @@ -3,50 +3,22 @@ use std::collections::HashSet; use cairo_vm::serde::deserialize_program::BuiltinName; use num_bigint::BigInt; use pretty_assertions::assert_eq; -use regex::Regex; -use rstest::rstest; use starknet_api::core::{EntryPointSelector, PatriciaKey}; -use starknet_api::deprecated_contract_class::{EntryPointOffset, EntryPointType}; use starknet_api::hash::{StarkFelt, StarkHash}; -use starknet_api::transaction::{Calldata, TransactionVersion}; +use starknet_api::transaction::Calldata; use starknet_api::{calldata, stark_felt}; use crate::abi::abi_utils::{get_storage_var_address, selector_from_name}; -use crate::context::{BlockContext, ChainInfo}; +use crate::context::ChainInfo; use crate::execution::call_info::{CallExecution, CallInfo, Retdata}; -use crate::execution::contract_class::ContractClass; use crate::execution::entry_point::CallEntryPoint; use crate::state::cached_state::CachedState; use crate::test_utils::contracts::FeatureContract; use crate::test_utils::dict_state_reader::DictStateReader; use crate::test_utils::initial_test_state::test_state; -use crate::test_utils::{ - create_calldata, - trivial_external_entry_point_new, - CairoVersion, - NonceManager, - BALANCE, -}; -use crate::transaction::account_transaction::AccountTransaction; -use crate::transaction::constants::{ - EXECUTE_ENTRY_POINT_NAME, - VALIDATE_DECLARE_ENTRY_POINT_NAME, - VALIDATE_DEPLOY_ENTRY_POINT_NAME, - VALIDATE_ENTRY_POINT_NAME, -}; -use crate::transaction::test_utils::{ - block_context, - create_account_tx_for_validate_test, - run_invoke_tx, - FaultyAccountTxCreatorArgs, - INVALID, -}; -use crate::transaction::transaction_types::TransactionType; -use crate::transaction::transactions::ExecutableTransaction; +use crate::test_utils::{trivial_external_entry_point_new, CairoVersion, BALANCE}; use crate::versioned_constants::VersionedConstants; -use crate::{invoke_tx_args, retdata, storage_key}; - -const INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET: usize = 117; +use crate::{retdata, storage_key}; #[test] fn test_call_info_iteration() { @@ -518,7 +490,6 @@ fn test_post_run_validation_security_failure() { } // Tests correct update of the fields: `storage_read_values` and `accessed_storage_keys`. -// Note read values also contain the reads performed right before a write operation. #[test] fn test_storage_related_members() { let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); @@ -530,7 +501,7 @@ fn test_storage_related_members() { ..trivial_external_entry_point_new(test_contract) }; let actual_call_info = entry_point_call.execute_directly(&mut state).unwrap(); - assert_eq!(actual_call_info.storage_read_values, vec![stark_felt!(0_u8), stark_felt!(39_u8)]); + assert_eq!(actual_call_info.storage_read_values, vec![stark_felt!(39_u8)]); assert_eq!( actual_call_info.accessed_storage_keys, HashSet::from([get_storage_var_address("number_map", &[stark_felt!(1_u8)])]) @@ -546,7 +517,7 @@ fn test_storage_related_members() { ..trivial_external_entry_point_new(test_contract) }; let actual_call_info = entry_point_call.execute_directly(&mut state).unwrap(); - assert_eq!(actual_call_info.storage_read_values, vec![stark_felt!(0_u8), value]); + assert_eq!(actual_call_info.storage_read_values, vec![value]); assert_eq!(actual_call_info.accessed_storage_keys, HashSet::from([storage_key!(key)])); } @@ -571,536 +542,3 @@ fn test_cairo1_entry_point_segment_arena() { .contains_key(BuiltinName::segment_arena.name()) ); } - -/// Fetch PC locations from the compiled contract to compute the expected PC locations in the -/// traceback. Computation is not robust, but as long as the cairo function itself is not edited, -/// this computation should be stable. -fn get_entry_point_offset( - contract_class: &ContractClass, - entry_point_selector: EntryPointSelector, -) -> EntryPointOffset { - match contract_class { - ContractClass::V0(class) => { - class - .entry_points_by_type - .get(&EntryPointType::External) - .unwrap() - .iter() - .find(|ep| ep.selector == entry_point_selector) - .unwrap() - .offset - } - ContractClass::V1(class) => { - class - .entry_points_by_type - .get(&EntryPointType::External) - .unwrap() - .iter() - .find(|ep| ep.selector == entry_point_selector) - .unwrap() - .offset - } - } -} - -#[rstest] -fn test_stack_trace( - block_context: BlockContext, - #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, -) { - let chain_info = ChainInfo::create_for_testing(); - let account = FeatureContract::AccountWithoutValidations(cairo_version); - let test_contract = FeatureContract::TestContract(cairo_version); - let mut state = test_state(&chain_info, BALANCE, &[(account, 1), (test_contract, 2)]); - let account_address = account.get_instance_address(0); - let test_contract_address = test_contract.get_instance_address(0); - let test_contract_address_2 = test_contract.get_instance_address(1); - let account_address_felt = *account_address.0.key(); - let test_contract_address_felt = *test_contract_address.0.key(); - let test_contract_address_2_felt = *test_contract_address_2.0.key(); - let test_contract_hash = test_contract.get_class_hash().0; - let account_contract_hash = account.get_class_hash().0; - - // Nest calls: __execute__ -> test_call_contract -> assert_0_is_1. - let call_contract_function_name = "test_call_contract"; - let inner_entry_point_selector_felt = selector_from_name("fail").0; - let calldata = create_calldata( - test_contract_address, // contract_address - call_contract_function_name, - &[ - test_contract_address_2_felt, // Contract address. - inner_entry_point_selector_felt, // Function selector. - stark_felt!(0_u8), // Innermost calldata length. - ], - ); - - let tx_execution_error = run_invoke_tx( - &mut state, - &block_context, - invoke_tx_args! { - sender_address: account_address, - calldata, - version: TransactionVersion::ZERO, - }, - ) - .unwrap_err(); - - // Fetch PC locations from the compiled contract to compute the expected PC locations in the - // traceback. Computation is not robust, but as long as the cairo function itself is not edited, - // this computation should be stable. - let account_contract_class = account.get_class(); - let account_entry_point_offset = get_entry_point_offset( - &account_contract_class, - selector_from_name(EXECUTE_ENTRY_POINT_NAME), - ); - let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; - let contract_class = test_contract.get_class(); - let external_entry_point_selector_felt = selector_from_name(call_contract_function_name).0; - let entry_point_offset = - get_entry_point_offset(&contract_class, selector_from_name(call_contract_function_name)); - // Relative offsets of the test_call_contract entry point and the inner call. - let call_location = entry_point_offset.0 + 14; - let entry_point_location = entry_point_offset.0 - 3; - // Relative offsets of the account contract. - let account_call_location = account_entry_point_offset.0 + 18; - let account_entry_point_location = account_entry_point_offset.0 - 8; - - let expected_trace_cairo0 = format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:7: -Cairo traceback (most recent call last): -Unknown location (pc=0:{account_call_location}) -Unknown location (pc=0:{account_entry_point_location}) - -1: Error in the called contract (contract address: {test_contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {external_entry_point_selector_felt}): -Error at pc=0:37: -Cairo traceback (most recent call last): -Unknown location (pc=0:{call_location}) -Unknown location (pc=0:{entry_point_location}) - -2: Error in the called contract (contract address: {test_contract_address_2_felt}, class hash: \ - {test_contract_hash}, selector: {inner_entry_point_selector_felt}): -Error at pc=0:1184: -Cairo traceback (most recent call last): -Unknown location (pc=0:1188) - -An ASSERT_EQ instruction failed: 1 != 0. -" - ); - - let expected_trace_cairo1 = format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:767: -1: Error in the called contract (contract address: {test_contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {external_entry_point_selector_felt}): -Error at pc=0:612: -2: Error in the called contract (contract address: {test_contract_address_2_felt}, class hash: \ - {test_contract_hash}, selector: {inner_entry_point_selector_felt}): -Execution failed. Failure reason: 0x6661696c ('fail'). -" - ); - - let expected_trace = match cairo_version { - CairoVersion::Cairo0 => expected_trace_cairo0, - CairoVersion::Cairo1 => expected_trace_cairo1, - }; - - assert_eq!(tx_execution_error.to_string(), expected_trace); -} - -#[rstest] -#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:37", (1081_u16, 1127_u16))] -#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", (1184_u16, 1135_u16))] -#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", (0_u16, 0_u16))] -#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", (0_u16, 0_u16))] -fn test_trace_callchain_ends_with_regular_call( - block_context: BlockContext, - #[case] cairo_version: CairoVersion, - #[case] last_func_name: &str, - #[case] expected_error: &str, - #[case] expected_pc_locations: (u16, u16), -) { - let chain_info = ChainInfo::create_for_testing(); - let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); - let test_contract = FeatureContract::TestContract(cairo_version); - let mut state = test_state(&chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); - - let account_address = account_contract.get_instance_address(0); - let test_contract_address = test_contract.get_instance_address(0); - let account_address_felt = *account_address.0.key(); - let contract_address_felt = *test_contract_address.0.key(); - let test_contract_hash = test_contract.get_class_hash().0; - let account_contract_hash = account_contract.get_class_hash().0; - - // invoke_call_chain -> call_contract_syscall invoke_call_chain -> regular call to final func. - let invoke_call_chain_selector = selector_from_name("invoke_call_chain"); - let invoke_call_chain_selector_felt = invoke_call_chain_selector.0; - - let calldata = create_calldata( - test_contract_address, // contract_address - "invoke_call_chain", - &[ - stark_felt!(7_u8), // Calldata length - contract_address_felt, // Contract address. - invoke_call_chain_selector_felt, // Function selector. - stark_felt!(0_u8), // Call type: call_contract_syscall. - stark_felt!(3_u8), // Calldata length - contract_address_felt, // Contract address. - selector_from_name(last_func_name).0, // Function selector. - stark_felt!(2_u8), // Call type: regular call. - ], - ); - - let tx_execution_error = run_invoke_tx( - &mut state, - &block_context, - invoke_tx_args! { - sender_address: account_address, - calldata, - version: TransactionVersion::ZERO, - }, - ) - .unwrap_err(); - - let account_entry_point_offset = get_entry_point_offset( - &account_contract.get_class(), - selector_from_name(EXECUTE_ENTRY_POINT_NAME), - ); - let entry_point_offset = - get_entry_point_offset(&test_contract.get_class(), invoke_call_chain_selector); - let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; - - let expected_trace = match cairo_version { - CairoVersion::Cairo0 => { - let call_location = entry_point_offset.0 + 12; - let entry_point_location = entry_point_offset.0 - 61; - // Relative offsets of the account contract. - let account_call_location = account_entry_point_offset.0 + 18; - let account_entry_point_location = account_entry_point_offset.0 - 8; - // Final invocation locations. - let (expected_pc0, expected_pc1) = expected_pc_locations; - format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:7: -Cairo traceback (most recent call last): -Unknown location (pc=0:{account_call_location}) -Unknown location (pc=0:{account_entry_point_location}) - -1: Error in the called contract (contract address: {contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:37: -Cairo traceback (most recent call last): -Unknown location (pc=0:{call_location}) -Unknown location (pc=0:{entry_point_location}) - -2: Error in the called contract (contract address: {contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:{expected_pc0}: -Cairo traceback (most recent call last): -Unknown location (pc=0:{call_location}) -Unknown location (pc=0:{expected_pc1}) - -{expected_error} -" - ) - } - CairoVersion::Cairo1 => { - let pc_location = entry_point_offset.0 + INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET; - format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:767: -1: Error in the called contract (contract address: {contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:9193: -Cairo traceback (most recent call last): -Unknown location (pc=0:{pc_location}) - -2: Error in the called contract (contract address: {contract_address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Execution failed. Failure reason: {expected_error}. -" - ) - } - }; - - assert_eq!(tx_execution_error.to_string(), expected_trace); -} - -#[rstest] -#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:23", 1_u8, 0_u8, (37_u16, 1093_u16, 1081_u16, 1166_u16))] -#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:23", 1_u8, 1_u8, (49_u16, 1111_u16, 1081_u16, 1166_u16))] -#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", 0_u8, 0_u8, (37_u16, 1093_u16, 1184_u16, 1188_u16))] -#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", 0_u8, 1_u8, (49_u16, 1111_u16, 1184_u16, 1188_u16))] -#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", 1_u8, 0_u8, (9193_u16, 0_u16, 0_u16, 0_u16))] -#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", 1_u8, 1_u8, (9262_u16, 0_u16, 0_u16, 0_u16))] -#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", 0_u8, 0_u8, (9193_u16, 0_u16, 0_u16, 0_u16))] -#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", 0_u8, 1_u8, (9262_u16, 0_u16, 0_u16, 0_u16))] -fn test_trace_call_chain_with_syscalls( - block_context: BlockContext, - #[case] cairo_version: CairoVersion, - #[case] last_func_name: &str, - #[case] expected_error: &str, - #[case] calldata_extra_length: u8, - #[case] call_type: u8, - #[case] expected_pcs: (u16, u16, u16, u16), -) { - let chain_info = ChainInfo::create_for_testing(); - let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); - let test_contract = FeatureContract::TestContract(cairo_version); - let mut state = test_state(&chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); - - let account_address = account_contract.get_instance_address(0); - let test_contract_address = test_contract.get_instance_address(0); - - let test_contract_hash = test_contract.get_class_hash().0; - let account_contract_hash = account_contract.get_class_hash().0; - let account_address_felt = *account_address.0.key(); - let address_felt = *test_contract_address.0.key(); - let contract_id = if call_type == 0 { address_felt } else { test_contract_hash }; - - // invoke_call_chain -> call_contract_syscall invoke_call_chain -> call_contract_syscall / - // library_call_syscall to final func. - let invoke_call_chain_selector = selector_from_name("invoke_call_chain"); - let invoke_call_chain_selector_felt = invoke_call_chain_selector.0; - let last_func_selector_felt = selector_from_name(last_func_name).0; - - let mut raw_calldata = vec![ - stark_felt!(7_u8 + calldata_extra_length), // Calldata length - address_felt, // Contract address. - invoke_call_chain_selector_felt, // Function selector. - stark_felt!(0_u8), // Call type: call_contract_syscall. - stark_felt!(3_u8 + calldata_extra_length), // Calldata length - contract_id, // Contract address / class hash. - last_func_selector_felt, // Function selector. - stark_felt!(call_type), // Syscall type: library_call or call_contract. - ]; - - // Need to send an empty array for the last call in `invoke_call_chain` variant. - if last_func_name == "invoke_call_chain" { - raw_calldata.push(stark_felt!(0_u8)); - } - - let calldata = create_calldata( - test_contract_address, // contract_address - "invoke_call_chain", - &raw_calldata, - ); - - let tx_execution_error = run_invoke_tx( - &mut state, - &block_context, - invoke_tx_args! { - sender_address: account_address, - calldata, - version: TransactionVersion::ZERO, - }, - ) - .unwrap_err(); - - let account_entry_point_offset = get_entry_point_offset( - &account_contract.get_class(), - selector_from_name(EXECUTE_ENTRY_POINT_NAME), - ); - let entry_point_offset = - get_entry_point_offset(&test_contract.get_class(), invoke_call_chain_selector); - let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; - - let last_call_preamble = if call_type == 0 { - format!( - "Error in the called contract (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {last_func_selector_felt})" - ) - } else { - format!( - "Error in a library call (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {last_func_selector_felt})" - ) - }; - - let expected_trace = match cairo_version { - CairoVersion::Cairo0 => { - let call_location = entry_point_offset.0 + 12; - let entry_point_location = entry_point_offset.0 - 61; - // Relative offsets of the account contract. - let account_call_location = account_entry_point_offset.0 + 18; - let account_entry_point_location = account_entry_point_offset.0 - 8; - let (expected_pc0, expected_pc1, expected_pc2, expected_pc3) = expected_pcs; - format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:7: -Cairo traceback (most recent call last): -Unknown location (pc=0:{account_call_location}) -Unknown location (pc=0:{account_entry_point_location}) - -1: Error in the called contract (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:37: -Cairo traceback (most recent call last): -Unknown location (pc=0:{call_location}) -Unknown location (pc=0:{entry_point_location}) - -2: Error in the called contract (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:{expected_pc0}: -Cairo traceback (most recent call last): -Unknown location (pc=0:{call_location}) -Unknown location (pc=0:{expected_pc1}) - -3: {last_call_preamble}: -Error at pc=0:{expected_pc2}: -Cairo traceback (most recent call last): -Unknown location (pc=0:{expected_pc3}) - -{expected_error} -" - ) - } - CairoVersion::Cairo1 => { - let pc_location = entry_point_offset.0 + INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET; - let expected_pc = expected_pcs.0; - format!( - "Transaction execution has failed: -0: Error in the called contract (contract address: {account_address_felt}, class hash: \ - {account_contract_hash}, selector: {execute_selector_felt}): -Error at pc=0:767: -1: Error in the called contract (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:9193: -Cairo traceback (most recent call last): -Unknown location (pc=0:{pc_location}) - -2: Error in the called contract (contract address: {address_felt}, class hash: \ - {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): -Error at pc=0:{expected_pc}: -Cairo traceback (most recent call last): -Unknown location (pc=0:{pc_location}) - -3: {last_call_preamble}: -Execution failed. Failure reason: {expected_error}. -" - ) - } - }; - - assert_eq!(tx_execution_error.to_string(), expected_trace); -} - -// TODO(Arni, 1/5/2024): Cover version 0 declare transaction. -// TODO(Arni, 1/5/2024): Consider version 0 invoke. -#[rstest] -#[case::validate_version_1( - TransactionType::InvokeFunction, - VALIDATE_ENTRY_POINT_NAME, - TransactionVersion::ONE -)] -#[case::validate_version_3( - TransactionType::InvokeFunction, - VALIDATE_ENTRY_POINT_NAME, - TransactionVersion::THREE -)] -#[case::validate_declare_version_1( - TransactionType::Declare, - VALIDATE_DECLARE_ENTRY_POINT_NAME, - TransactionVersion::ONE -)] -#[case::validate_declare_version_2( - TransactionType::Declare, - VALIDATE_DECLARE_ENTRY_POINT_NAME, - TransactionVersion::TWO -)] -#[case::validate_declare_version_3( - TransactionType::Declare, - VALIDATE_DECLARE_ENTRY_POINT_NAME, - TransactionVersion::THREE -)] -#[case::validate_deploy_version_1( - TransactionType::DeployAccount, - VALIDATE_DEPLOY_ENTRY_POINT_NAME, - TransactionVersion::ONE -)] -#[case::validate_deploy_version_3( - TransactionType::DeployAccount, - VALIDATE_DEPLOY_ENTRY_POINT_NAME, - TransactionVersion::THREE -)] -fn test_validate_trace( - #[case] tx_type: TransactionType, - #[case] entry_point_name: &str, - #[case] tx_version: TransactionVersion, - #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, -) { - let create_for_account_testing = &BlockContext::create_for_account_testing(); - let block_context = create_for_account_testing; - let faulty_account = FeatureContract::FaultyAccount(cairo_version); - let mut sender_address = faulty_account.get_instance_address(0); - let class_hash = faulty_account.get_class_hash(); - let state = &mut test_state(&block_context.chain_info, 0, &[(faulty_account, 1)]); - let selector = selector_from_name(entry_point_name).0; - - // Logic failure. - let account_tx = create_account_tx_for_validate_test( - &mut NonceManager::default(), - FaultyAccountTxCreatorArgs { - scenario: INVALID, - tx_type, - tx_version, - sender_address, - class_hash, - ..Default::default() - }, - ); - - if let TransactionType::DeployAccount = tx_type { - // Deploy account uses the actual address as the sender address. - match &account_tx { - AccountTransaction::DeployAccount(tx) => { - sender_address = tx.contract_address; - } - _ => panic!("Expected DeployAccountTransaction type"), - } - } - - let contract_address = *sender_address.0.key(); - - let expected_error = match cairo_version { - CairoVersion::Cairo0 => format!( - "Transaction validation has failed: -0: Error in the called contract (contract address: {contract_address}, class hash: {class_hash}, \ - selector: {selector}): -Error at pc=0:0: -Cairo traceback (most recent call last): -Unknown location (pc=0:0) -Unknown location (pc=0:0) - -An ASSERT_EQ instruction failed: 1 != 0. -" - ), - CairoVersion::Cairo1 => format!( - "Transaction validation has failed: -0: Error in the called contract (contract address: {contract_address}, class hash: {class_hash}, \ - selector: {selector}): -Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid scenario'). -" - ), - }; - - // Clean pc locations from the trace. - let re = Regex::new(r"pc=0:[0-9]+").unwrap(); - let cleaned_expected_error = &re.replace_all(&expected_error, "pc=0:*"); - let actual_error = account_tx.execute(state, block_context, true, true).unwrap_err(); - let actual_error_str = actual_error.to_string(); - let cleaned_actual_error = &re.replace_all(&actual_error_str, "pc=0:*"); - // Compare actual trace to the expected trace (sans pc locations). - assert_eq!(cleaned_actual_error.to_string(), cleaned_expected_error.to_string()); -} diff --git a/crates/blockifier/src/execution/errors.rs b/crates/blockifier/src/execution/errors.rs index 8efb54bca3e..b2cec45d2c6 100644 --- a/crates/blockifier/src/execution/errors.rs +++ b/crates/blockifier/src/execution/errors.rs @@ -1,23 +1,18 @@ use cairo_vm::types::errors::math_errors::MathError; use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; -use cairo_vm::vm::errors::hint_errors::HintError; use cairo_vm::vm::errors::memory_errors::MemoryError; use cairo_vm::vm::errors::runner_errors::RunnerError; use cairo_vm::vm::errors::trace_errors::TraceError; use cairo_vm::vm::errors::vm_errors::VirtualMachineError; -use cairo_vm::vm::errors::vm_exception::VmException; use num_bigint::{BigInt, TryFromBigIntError}; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; use starknet_api::deprecated_contract_class::EntryPointType; use starknet_api::hash::StarkFelt; use thiserror::Error; -use super::deprecated_syscalls::hint_processor::DeprecatedSyscallExecutionError; -use super::syscalls::hint_processor::SyscallExecutionError; use crate::execution::entry_point::ConstructorContext; use crate::execution::execution_utils::format_panic_data; use crate::state::errors::StateError; -use crate::transaction::errors::TransactionExecutionError; // TODO(AlonH, 21/12/2022): Implement Display for all types that appear in errors. @@ -102,7 +97,7 @@ pub enum EntryPointExecutionError { #[derive(Debug, Error)] pub enum ConstructorEntryPointExecutionError { #[error( - "Error in the contract class {class_hash:?} constructor (selector: \ + "Error in the contract class {class_hash} constructor (selector: \ {constructor_selector:?}, address: {contract_address:?}): {error}" )] ExecutionError { @@ -140,256 +135,3 @@ pub enum ContractClassError { sierra_program_length: usize, }, } - -// A set of functions used to extract error trace from a recursive error object. - -pub const TRACE_LENGTH_CAP: usize = 15000; -pub const TRACE_EXTRA_CHARS_SLACK: usize = 100; - -/// Extracts the error trace from a `TransactionExecutionError`. This is a top level function. -pub fn gen_transaction_execution_error_trace(error: &TransactionExecutionError) -> String { - let mut error_stack: Vec = Vec::new(); - - match error { - TransactionExecutionError::ExecutionError { - error, - class_hash, - storage_address, - selector, - } - | TransactionExecutionError::ValidateTransactionError { - error, - class_hash, - storage_address, - selector, - } - | TransactionExecutionError::ContractConstructorExecutionFailed( - ConstructorEntryPointExecutionError::ExecutionError { - error, - class_hash, - contract_address: storage_address, - // TODO(Dori, 5/5/2024): Also handle the no-selector case. - constructor_selector: Some(selector), - }, - ) => { - let depth: usize = 0; - error_stack.push(format!( - "{}: Error in the called contract (contract address: {}, class hash: {}, \ - selector: {}):", - depth, - *storage_address.0.key(), - class_hash, - selector.0 - )); - extract_entry_point_execution_error_into_stack_trace( - &mut error_stack, - depth + 1, - error, - ); - } - _ => { - error_stack.push(error.to_string()); - } - } - - let error_stack_str = error_stack.join("\n"); - - // When the trace string is too long, trim it in a way that keeps both the beginning and end. - if error_stack_str.len() > TRACE_LENGTH_CAP + TRACE_EXTRA_CHARS_SLACK { - error_stack_str[..(TRACE_LENGTH_CAP / 2)].to_string() - + "\n\n...\n\n" - + &error_stack_str[(error_stack_str.len() - TRACE_LENGTH_CAP / 2)..] - } else { - error_stack_str - } -} - -fn extract_cairo_run_error_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - error: &CairoRunError, -) { - if let CairoRunError::VmException(vm_exception) = error { - return extract_vm_exception_into_stack_trace(error_stack, depth, vm_exception); - } - error_stack.push(error.to_string()); -} - -fn extract_vm_exception_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - vm_exception: &VmException, -) { - let vm_exception_preamble = format!("Error at pc=0:{}:", vm_exception.pc); - error_stack.push(vm_exception_preamble); - - if let Some(traceback) = &vm_exception.traceback { - error_stack.push(traceback.to_string()); - } - extract_virtual_machine_error_into_stack_trace(error_stack, depth, &vm_exception.inner_exc) -} - -fn extract_virtual_machine_error_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - vm_error: &VirtualMachineError, -) { - match vm_error { - VirtualMachineError::Hint(ref boxed_hint_error) => { - if let HintError::Internal(internal_vm_error) = &boxed_hint_error.1 { - return extract_virtual_machine_error_into_stack_trace( - error_stack, - depth, - internal_vm_error, - ); - } - error_stack.push(boxed_hint_error.1.to_string()); - } - VirtualMachineError::Other(anyhow_error) => { - let syscall_exec_err = anyhow_error.downcast_ref::(); - if let Some(downcast_anyhow) = syscall_exec_err { - extract_syscall_execution_error_into_stack_trace( - error_stack, - depth, - downcast_anyhow, - ) - } else { - let deprecated_syscall_exec_err = - anyhow_error.downcast_ref::(); - if let Some(downcast_anyhow) = deprecated_syscall_exec_err { - extract_deprecated_syscall_execution_error_into_stack_trace( - error_stack, - depth, - downcast_anyhow, - ) - } - } - } - _ => { - error_stack.push(format!("{}\n", vm_error)); - } - } -} - -fn extract_syscall_execution_error_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - syscall_error: &SyscallExecutionError, -) { - match syscall_error { - SyscallExecutionError::CallContractExecutionError { - class_hash, - storage_address, - selector, - error, - } => { - let call_contract_preamble = format!( - "{}: Error in the called contract (contract address: {}, class hash: {}, \ - selector: {}):", - depth, - storage_address.0.key(), - class_hash, - selector.0 - ); - error_stack.push(call_contract_preamble); - extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error) - } - SyscallExecutionError::LibraryCallExecutionError { - class_hash, - storage_address, - selector, - error, - } => { - let libcall_preamble = format!( - "{}: Error in a library call (contract address: {}, class hash: {}, selector: {}):", - depth, - storage_address.0.key(), - class_hash, - selector.0 - ); - error_stack.push(libcall_preamble); - extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error); - } - SyscallExecutionError::EntryPointExecutionError(entry_point_error) => { - extract_entry_point_execution_error_into_stack_trace( - error_stack, - depth, - entry_point_error, - ) - } - _ => { - error_stack.push(syscall_error.to_string()); - } - } -} - -fn extract_deprecated_syscall_execution_error_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - syscall_error: &DeprecatedSyscallExecutionError, -) { - match syscall_error { - DeprecatedSyscallExecutionError::CallContractExecutionError { - class_hash, - storage_address, - selector, - error, - } => { - let call_contract_preamble = format!( - "{}: Error in the called contract (contract address: {}, class hash: {}, \ - selector: {}):", - depth, - storage_address.0.key(), - class_hash, - selector.0 - ); - error_stack.push(call_contract_preamble); - extract_deprecated_syscall_execution_error_into_stack_trace( - error_stack, - depth + 1, - error, - ) - } - DeprecatedSyscallExecutionError::LibraryCallExecutionError { - class_hash, - storage_address, - selector, - error, - } => { - let libcall_preamble = format!( - "{}: Error in a library call (contract address: {}, class hash: {}, selector: {}):", - depth, - storage_address.0.key(), - class_hash, - selector.0 - ); - error_stack.push(libcall_preamble); - extract_deprecated_syscall_execution_error_into_stack_trace( - error_stack, - depth + 1, - error, - ) - } - DeprecatedSyscallExecutionError::EntryPointExecutionError(entry_point_error) => { - extract_entry_point_execution_error_into_stack_trace( - error_stack, - depth, - entry_point_error, - ) - } - _ => error_stack.push(syscall_error.to_string()), - } -} - -fn extract_entry_point_execution_error_into_stack_trace( - error_stack: &mut Vec, - depth: usize, - entry_point_error: &EntryPointExecutionError, -) { - match entry_point_error { - EntryPointExecutionError::CairoRunError(cairo_run_error) => { - extract_cairo_run_error_into_stack_trace(error_stack, depth, cairo_run_error) - } - _ => error_stack.push(format!("{}\n", entry_point_error)), - } -} diff --git a/crates/blockifier/src/execution/stack_trace.rs b/crates/blockifier/src/execution/stack_trace.rs new file mode 100644 index 00000000000..709758131ff --- /dev/null +++ b/crates/blockifier/src/execution/stack_trace.rs @@ -0,0 +1,437 @@ +use cairo_vm::vm::errors::cairo_run_errors::CairoRunError; +use cairo_vm::vm::errors::hint_errors::HintError; +use cairo_vm::vm::errors::vm_errors::VirtualMachineError; +use itertools::Itertools; +use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector}; + +use super::deprecated_syscalls::hint_processor::DeprecatedSyscallExecutionError; +use super::syscalls::hint_processor::SyscallExecutionError; +use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError}; +use crate::transaction::errors::TransactionExecutionError; + +#[cfg(test)] +#[path = "stack_trace_test.rs"] +pub mod test; + +pub const TRACE_LENGTH_CAP: usize = 15000; +pub const TRACE_EXTRA_CHARS_SLACK: usize = 100; + +enum PreambleType { + CallContract, + LibraryCall, + Constructor, +} + +impl PreambleType { + pub fn text(&self) -> &str { + match self { + Self::CallContract => "Error in the called contract", + Self::LibraryCall => "Error in a library call", + Self::Constructor => "Error in the contract class constructor", + } + } +} + +pub struct EntryPointErrorFrame { + depth: usize, + preamble_type: PreambleType, + storage_address: ContractAddress, + class_hash: ClassHash, + selector: Option, +} + +impl EntryPointErrorFrame { + fn preamble_text(&self) -> String { + format!( + "{}: {} (contract address: {}, class hash: {}, selector: {}):", + self.depth, + self.preamble_type.text(), + self.storage_address.0.key(), + self.class_hash, + if let Some(selector) = self.selector { + format!("{}", selector.0) + } else { + "UNKNOWN".to_string() + } + ) + } +} + +impl From<&EntryPointErrorFrame> for String { + fn from(value: &EntryPointErrorFrame) -> Self { + value.preamble_text() + } +} + +pub struct VmExceptionFrame { + pc: usize, + traceback: Option, +} + +impl From<&VmExceptionFrame> for String { + fn from(value: &VmExceptionFrame) -> Self { + let vm_exception_preamble = format!("Error at pc=0:{}:", value.pc); + let vm_exception_traceback = if let Some(traceback) = &value.traceback { + format!("\n{}", traceback) + } else { + "".to_string() + }; + format!("{vm_exception_preamble}{vm_exception_traceback}") + } +} + +pub enum Frame { + EntryPoint(EntryPointErrorFrame), + Vm(VmExceptionFrame), + StringFrame(String), +} + +impl From<&Frame> for String { + fn from(value: &Frame) -> Self { + match value { + Frame::EntryPoint(entry_point_frame) => entry_point_frame.into(), + Frame::Vm(vm_exception_frame) => vm_exception_frame.into(), + Frame::StringFrame(error) => error.clone(), + } + } +} + +impl From for Frame { + fn from(value: EntryPointErrorFrame) -> Self { + Frame::EntryPoint(value) + } +} + +impl From for Frame { + fn from(value: VmExceptionFrame) -> Self { + Frame::Vm(value) + } +} + +impl From for Frame { + fn from(value: String) -> Self { + Frame::StringFrame(value) + } +} + +#[derive(Default)] +pub struct ErrorStack { + stack: Vec, +} + +impl From for String { + fn from(value: ErrorStack) -> Self { + let error_stack_str = value.stack.iter().map(String::from).join("\n"); + + // When the trace string is too long, trim it in a way that keeps both the beginning and + // end. + if error_stack_str.len() > TRACE_LENGTH_CAP + TRACE_EXTRA_CHARS_SLACK { + error_stack_str[..(TRACE_LENGTH_CAP / 2)].to_string() + + "\n\n...\n\n" + + &error_stack_str[(error_stack_str.len() - TRACE_LENGTH_CAP / 2)..] + } else { + error_stack_str + } + } +} + +impl ErrorStack { + pub fn push(&mut self, frame: Frame) { + self.stack.push(frame); + } +} + +/// Extracts the error trace from a `TransactionExecutionError`. This is a top level function. +pub fn gen_transaction_execution_error_trace(error: &TransactionExecutionError) -> ErrorStack { + match error { + TransactionExecutionError::ExecutionError { + error, + class_hash, + storage_address, + selector, + } + | TransactionExecutionError::ValidateTransactionError { + error, + class_hash, + storage_address, + selector, + } => gen_error_trace_from_entry_point_error( + error, + storage_address, + class_hash, + Some(selector), + PreambleType::CallContract, + ), + TransactionExecutionError::ContractConstructorExecutionFailed( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address: storage_address, + constructor_selector, + }, + ) => gen_error_trace_from_entry_point_error( + error, + storage_address, + class_hash, + constructor_selector.as_ref(), + PreambleType::Constructor, + ), + _ => { + // Top-level error is unrelated to Cairo execution, no "real" frames. + let mut stack = ErrorStack::default(); + stack.push(Frame::StringFrame(error.to_string())); + stack + } + } +} + +/// Generate error stack from top-level entry point execution error. +fn gen_error_trace_from_entry_point_error( + error: &EntryPointExecutionError, + storage_address: &ContractAddress, + class_hash: &ClassHash, + entry_point_selector: Option<&EntryPointSelector>, + preamble_type: PreambleType, +) -> ErrorStack { + let mut error_stack: ErrorStack = ErrorStack::default(); + let depth = 0; + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type, + storage_address: *storage_address, + class_hash: *class_hash, + selector: entry_point_selector.copied(), + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(&mut error_stack, depth + 1, error); + error_stack +} + +fn extract_cairo_run_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + error: &CairoRunError, +) { + if let CairoRunError::VmException(vm_exception) = error { + error_stack.push( + VmExceptionFrame { pc: vm_exception.pc, traceback: vm_exception.traceback.clone() } + .into(), + ); + extract_virtual_machine_error_into_stack_trace(error_stack, depth, &vm_exception.inner_exc); + } else { + error_stack.push(error.to_string().into()); + } +} + +fn extract_virtual_machine_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + vm_error: &VirtualMachineError, +) { + match vm_error { + VirtualMachineError::Hint(ref boxed_hint_error) => { + if let HintError::Internal(internal_vm_error) = &boxed_hint_error.1 { + return extract_virtual_machine_error_into_stack_trace( + error_stack, + depth, + internal_vm_error, + ); + } + error_stack.push(boxed_hint_error.1.to_string().into()); + } + VirtualMachineError::Other(anyhow_error) => { + let syscall_exec_err = anyhow_error.downcast_ref::(); + if let Some(downcast_anyhow) = syscall_exec_err { + extract_syscall_execution_error_into_stack_trace( + error_stack, + depth, + downcast_anyhow, + ) + } else { + let deprecated_syscall_exec_err = + anyhow_error.downcast_ref::(); + if let Some(downcast_anyhow) = deprecated_syscall_exec_err { + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth, + downcast_anyhow, + ) + } + } + } + _ => { + error_stack.push(format!("{}\n", vm_error).into()); + } + } +} + +fn extract_syscall_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + syscall_error: &SyscallExecutionError, +) { + match syscall_error { + SyscallExecutionError::CallContractExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::CallContract, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error) + } + SyscallExecutionError::LibraryCallExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::LibraryCall, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_syscall_execution_error_into_stack_trace(error_stack, depth + 1, error); + } + SyscallExecutionError::ConstructorEntryPointExecutionError( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address, + constructor_selector, + }, + ) => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::Constructor, + storage_address: *contract_address, + class_hash: *class_hash, + selector: *constructor_selector, + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(error_stack, depth, error) + } + SyscallExecutionError::EntryPointExecutionError(entry_point_error) => { + extract_entry_point_execution_error_into_stack_trace( + error_stack, + depth, + entry_point_error, + ) + } + _ => { + error_stack.push(syscall_error.to_string().into()); + } + } +} + +fn extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + syscall_error: &DeprecatedSyscallExecutionError, +) { + match syscall_error { + DeprecatedSyscallExecutionError::CallContractExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::CallContract, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth + 1, + error, + ) + } + DeprecatedSyscallExecutionError::LibraryCallExecutionError { + class_hash, + storage_address, + selector, + error, + } => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::LibraryCall, + storage_address: *storage_address, + class_hash: *class_hash, + selector: Some(*selector), + } + .into(), + ); + extract_deprecated_syscall_execution_error_into_stack_trace( + error_stack, + depth + 1, + error, + ) + } + DeprecatedSyscallExecutionError::ConstructorEntryPointExecutionError( + ConstructorEntryPointExecutionError::ExecutionError { + error, + class_hash, + contract_address, + constructor_selector, + }, + ) => { + error_stack.push( + EntryPointErrorFrame { + depth, + preamble_type: PreambleType::Constructor, + storage_address: *contract_address, + class_hash: *class_hash, + selector: *constructor_selector, + } + .into(), + ); + extract_entry_point_execution_error_into_stack_trace(error_stack, depth, error) + } + DeprecatedSyscallExecutionError::EntryPointExecutionError(entry_point_error) => { + extract_entry_point_execution_error_into_stack_trace( + error_stack, + depth, + entry_point_error, + ) + } + _ => error_stack.push(syscall_error.to_string().into()), + } +} + +fn extract_entry_point_execution_error_into_stack_trace( + error_stack: &mut ErrorStack, + depth: usize, + entry_point_error: &EntryPointExecutionError, +) { + match entry_point_error { + EntryPointExecutionError::CairoRunError(cairo_run_error) => { + extract_cairo_run_error_into_stack_trace(error_stack, depth, cairo_run_error) + } + _ => error_stack.push(format!("{}\n", entry_point_error).into()), + } +} diff --git a/crates/blockifier/src/execution/stack_trace_test.rs b/crates/blockifier/src/execution/stack_trace_test.rs new file mode 100644 index 00000000000..34bac750cb6 --- /dev/null +++ b/crates/blockifier/src/execution/stack_trace_test.rs @@ -0,0 +1,729 @@ +use pretty_assertions::assert_eq; +use regex::Regex; +use rstest::rstest; +use starknet_api::core::{calculate_contract_address, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::{ + Calldata, + ContractAddressSalt, + Fee, + TransactionSignature, + TransactionVersion, +}; +use starknet_api::{calldata, stark_felt}; + +use crate::abi::abi_utils::selector_from_name; +use crate::abi::constants::CONSTRUCTOR_ENTRY_POINT_NAME; +use crate::context::{BlockContext, ChainInfo}; +use crate::invoke_tx_args; +use crate::test_utils::contracts::FeatureContract; +use crate::test_utils::initial_test_state::{fund_account, test_state}; +use crate::test_utils::{create_calldata, CairoVersion, NonceManager, BALANCE}; +use crate::transaction::account_transaction::AccountTransaction; +use crate::transaction::constants::{ + DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME, + EXECUTE_ENTRY_POINT_NAME, + FELT_TRUE, + VALIDATE_DECLARE_ENTRY_POINT_NAME, + VALIDATE_DEPLOY_ENTRY_POINT_NAME, + VALIDATE_ENTRY_POINT_NAME, +}; +use crate::transaction::test_utils::{ + account_invoke_tx, + block_context, + create_account_tx_for_validate_test, + run_invoke_tx, + FaultyAccountTxCreatorArgs, + INVALID, +}; +use crate::transaction::transaction_types::TransactionType; +use crate::transaction::transactions::ExecutableTransaction; + +const INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET: usize = 117; + +#[rstest] +fn test_stack_trace( + block_context: BlockContext, + #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, +) { + let chain_info = ChainInfo::create_for_testing(); + let account = FeatureContract::AccountWithoutValidations(cairo_version); + let test_contract = FeatureContract::TestContract(cairo_version); + let mut state = test_state(&chain_info, BALANCE, &[(account, 1), (test_contract, 2)]); + let account_address = account.get_instance_address(0); + let test_contract_address = test_contract.get_instance_address(0); + let test_contract_address_2 = test_contract.get_instance_address(1); + let account_address_felt = *account_address.0.key(); + let test_contract_address_felt = *test_contract_address.0.key(); + let test_contract_address_2_felt = *test_contract_address_2.0.key(); + let test_contract_hash = test_contract.get_class_hash().0; + let account_contract_hash = account.get_class_hash().0; + + // Nest calls: __execute__ -> test_call_contract -> assert_0_is_1. + let call_contract_function_name = "test_call_contract"; + let inner_entry_point_selector_felt = selector_from_name("fail").0; + let calldata = create_calldata( + test_contract_address, // contract_address + call_contract_function_name, + &[ + test_contract_address_2_felt, // Contract address. + inner_entry_point_selector_felt, // Function selector. + stark_felt!(0_u8), // Innermost calldata length. + ], + ); + + let tx_execution_error = run_invoke_tx( + &mut state, + &block_context, + invoke_tx_args! { + sender_address: account_address, + calldata, + version: TransactionVersion::ZERO, + }, + ) + .unwrap_err(); + + // Fetch PC locations from the compiled contract to compute the expected PC locations in the + // traceback. Computation is not robust, but as long as the cairo function itself is not edited, + // this computation should be stable. + let account_entry_point_offset = + account.get_entry_point_offset(selector_from_name(EXECUTE_ENTRY_POINT_NAME)); + let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; + let external_entry_point_selector_felt = selector_from_name(call_contract_function_name).0; + let entry_point_offset = + test_contract.get_entry_point_offset(selector_from_name(call_contract_function_name)); + // Relative offsets of the test_call_contract entry point and the inner call. + let call_location = entry_point_offset.0 + 14; + let entry_point_location = entry_point_offset.0 - 3; + // Relative offsets of the account contract. + let account_call_location = account_entry_point_offset.0 + 18; + let account_entry_point_location = account_entry_point_offset.0 - 8; + + let expected_trace_cairo0 = format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:7: +Cairo traceback (most recent call last): +Unknown location (pc=0:{account_call_location}) +Unknown location (pc=0:{account_entry_point_location}) + +1: Error in the called contract (contract address: {test_contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {external_entry_point_selector_felt}): +Error at pc=0:37: +Cairo traceback (most recent call last): +Unknown location (pc=0:{call_location}) +Unknown location (pc=0:{entry_point_location}) + +2: Error in the called contract (contract address: {test_contract_address_2_felt}, class hash: \ + {test_contract_hash}, selector: {inner_entry_point_selector_felt}): +Error at pc=0:1184: +Cairo traceback (most recent call last): +Unknown location (pc=0:1188) + +An ASSERT_EQ instruction failed: 1 != 0. +" + ); + + let expected_trace_cairo1 = format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:767: +1: Error in the called contract (contract address: {test_contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {external_entry_point_selector_felt}): +Error at pc=0:612: +2: Error in the called contract (contract address: {test_contract_address_2_felt}, class hash: \ + {test_contract_hash}, selector: {inner_entry_point_selector_felt}): +Execution failed. Failure reason: 0x6661696c ('fail'). +" + ); + + let expected_trace = match cairo_version { + CairoVersion::Cairo0 => expected_trace_cairo0, + CairoVersion::Cairo1 => expected_trace_cairo1, + }; + + assert_eq!(tx_execution_error.to_string(), expected_trace); +} + +#[rstest] +#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:37", (1081_u16, 1127_u16))] +#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", (1184_u16, 1135_u16))] +#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", (0_u16, 0_u16))] +#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", (0_u16, 0_u16))] +fn test_trace_callchain_ends_with_regular_call( + block_context: BlockContext, + #[case] cairo_version: CairoVersion, + #[case] last_func_name: &str, + #[case] expected_error: &str, + #[case] expected_pc_locations: (u16, u16), +) { + let chain_info = ChainInfo::create_for_testing(); + let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); + let test_contract = FeatureContract::TestContract(cairo_version); + let mut state = test_state(&chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); + + let account_address = account_contract.get_instance_address(0); + let test_contract_address = test_contract.get_instance_address(0); + let account_address_felt = *account_address.0.key(); + let contract_address_felt = *test_contract_address.0.key(); + let test_contract_hash = test_contract.get_class_hash().0; + let account_contract_hash = account_contract.get_class_hash().0; + + // invoke_call_chain -> call_contract_syscall invoke_call_chain -> regular call to final func. + let invoke_call_chain_selector = selector_from_name("invoke_call_chain"); + let invoke_call_chain_selector_felt = invoke_call_chain_selector.0; + + let calldata = create_calldata( + test_contract_address, // contract_address + "invoke_call_chain", + &[ + stark_felt!(7_u8), // Calldata length + contract_address_felt, // Contract address. + invoke_call_chain_selector_felt, // Function selector. + stark_felt!(0_u8), // Call type: call_contract_syscall. + stark_felt!(3_u8), // Calldata length + contract_address_felt, // Contract address. + selector_from_name(last_func_name).0, // Function selector. + stark_felt!(2_u8), // Call type: regular call. + ], + ); + + let tx_execution_error = run_invoke_tx( + &mut state, + &block_context, + invoke_tx_args! { + sender_address: account_address, + calldata, + version: TransactionVersion::ZERO, + }, + ) + .unwrap_err(); + + let account_entry_point_offset = + account_contract.get_entry_point_offset(selector_from_name(EXECUTE_ENTRY_POINT_NAME)); + let entry_point_offset = test_contract.get_entry_point_offset(invoke_call_chain_selector); + let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; + + let expected_trace = match cairo_version { + CairoVersion::Cairo0 => { + let call_location = entry_point_offset.0 + 12; + let entry_point_location = entry_point_offset.0 - 61; + // Relative offsets of the account contract. + let account_call_location = account_entry_point_offset.0 + 18; + let account_entry_point_location = account_entry_point_offset.0 - 8; + // Final invocation locations. + let (expected_pc0, expected_pc1) = expected_pc_locations; + format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:7: +Cairo traceback (most recent call last): +Unknown location (pc=0:{account_call_location}) +Unknown location (pc=0:{account_entry_point_location}) + +1: Error in the called contract (contract address: {contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:37: +Cairo traceback (most recent call last): +Unknown location (pc=0:{call_location}) +Unknown location (pc=0:{entry_point_location}) + +2: Error in the called contract (contract address: {contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:{expected_pc0}: +Cairo traceback (most recent call last): +Unknown location (pc=0:{call_location}) +Unknown location (pc=0:{expected_pc1}) + +{expected_error} +" + ) + } + CairoVersion::Cairo1 => { + let pc_location = entry_point_offset.0 + INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET; + format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:767: +1: Error in the called contract (contract address: {contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:9228: +Cairo traceback (most recent call last): +Unknown location (pc=0:{pc_location}) + +2: Error in the called contract (contract address: {contract_address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Execution failed. Failure reason: {expected_error}. +" + ) + } + }; + + assert_eq!(tx_execution_error.to_string(), expected_trace); +} + +#[rstest] +#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:23", 1_u8, 0_u8, (37_u16, 1093_u16, 1081_u16, 1166_u16))] +#[case(CairoVersion::Cairo0, "invoke_call_chain", "Couldn't compute operand op0. Unknown value for memory cell 1:23", 1_u8, 1_u8, (49_u16, 1111_u16, 1081_u16, 1166_u16))] +#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", 0_u8, 0_u8, (37_u16, 1093_u16, 1184_u16, 1188_u16))] +#[case(CairoVersion::Cairo0, "fail", "An ASSERT_EQ instruction failed: 1 != 0.", 0_u8, 1_u8, (49_u16, 1111_u16, 1184_u16, 1188_u16))] +#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", 1_u8, 0_u8, (9228_u16, 9228_u16, 0_u16, 0_u16))] +#[case(CairoVersion::Cairo1, "invoke_call_chain", "0x4469766973696f6e2062792030 ('Division by 0')", 1_u8, 1_u8, (9228_u16, 9297_u16, 0_u16, 0_u16))] +#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", 0_u8, 0_u8, (9228_u16, 9228_u16, 0_u16, 0_u16))] +#[case(CairoVersion::Cairo1, "fail", "0x6661696c ('fail')", 0_u8, 1_u8, (9228_u16, 9297_u16, 0_u16, 0_u16))] +fn test_trace_call_chain_with_syscalls( + block_context: BlockContext, + #[case] cairo_version: CairoVersion, + #[case] last_func_name: &str, + #[case] expected_error: &str, + #[case] calldata_extra_length: u8, + #[case] call_type: u8, + #[case] expected_pcs: (u16, u16, u16, u16), +) { + let chain_info = ChainInfo::create_for_testing(); + let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); + let test_contract = FeatureContract::TestContract(cairo_version); + let mut state = test_state(&chain_info, BALANCE, &[(account_contract, 1), (test_contract, 1)]); + + let account_address = account_contract.get_instance_address(0); + let test_contract_address = test_contract.get_instance_address(0); + + let test_contract_hash = test_contract.get_class_hash().0; + let account_contract_hash = account_contract.get_class_hash().0; + let account_address_felt = *account_address.0.key(); + let address_felt = *test_contract_address.0.key(); + let contract_id = if call_type == 0 { address_felt } else { test_contract_hash }; + + // invoke_call_chain -> call_contract_syscall invoke_call_chain -> call_contract_syscall / + // library_call_syscall to final func. + let invoke_call_chain_selector = selector_from_name("invoke_call_chain"); + let invoke_call_chain_selector_felt = invoke_call_chain_selector.0; + let last_func_selector_felt = selector_from_name(last_func_name).0; + + let mut raw_calldata = vec![ + stark_felt!(7_u8 + calldata_extra_length), // Calldata length + address_felt, // Contract address. + invoke_call_chain_selector_felt, // Function selector. + stark_felt!(0_u8), // Call type: call_contract_syscall. + stark_felt!(3_u8 + calldata_extra_length), // Calldata length + contract_id, // Contract address / class hash. + last_func_selector_felt, // Function selector. + stark_felt!(call_type), // Syscall type: library_call or call_contract. + ]; + + // Need to send an empty array for the last call in `invoke_call_chain` variant. + if last_func_name == "invoke_call_chain" { + raw_calldata.push(stark_felt!(0_u8)); + } + + let calldata = create_calldata( + test_contract_address, // contract_address + "invoke_call_chain", + &raw_calldata, + ); + + let tx_execution_error = run_invoke_tx( + &mut state, + &block_context, + invoke_tx_args! { + sender_address: account_address, + calldata, + version: TransactionVersion::ZERO, + }, + ) + .unwrap_err(); + + let account_entry_point_offset = + account_contract.get_entry_point_offset(selector_from_name(EXECUTE_ENTRY_POINT_NAME)); + let entry_point_offset = test_contract.get_entry_point_offset(invoke_call_chain_selector); + let execute_selector_felt = selector_from_name(EXECUTE_ENTRY_POINT_NAME).0; + + let last_call_preamble = if call_type == 0 { + format!( + "Error in the called contract (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {last_func_selector_felt})" + ) + } else { + format!( + "Error in a library call (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {last_func_selector_felt})" + ) + }; + + let expected_trace = match cairo_version { + CairoVersion::Cairo0 => { + let call_location = entry_point_offset.0 + 12; + let entry_point_location = entry_point_offset.0 - 61; + // Relative offsets of the account contract. + let account_call_location = account_entry_point_offset.0 + 18; + let account_entry_point_location = account_entry_point_offset.0 - 8; + let (expected_pc0, expected_pc1, expected_pc2, expected_pc3) = expected_pcs; + format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:7: +Cairo traceback (most recent call last): +Unknown location (pc=0:{account_call_location}) +Unknown location (pc=0:{account_entry_point_location}) + +1: Error in the called contract (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:37: +Cairo traceback (most recent call last): +Unknown location (pc=0:{call_location}) +Unknown location (pc=0:{entry_point_location}) + +2: Error in the called contract (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:{expected_pc0}: +Cairo traceback (most recent call last): +Unknown location (pc=0:{call_location}) +Unknown location (pc=0:{expected_pc1}) + +3: {last_call_preamble}: +Error at pc=0:{expected_pc2}: +Cairo traceback (most recent call last): +Unknown location (pc=0:{expected_pc3}) + +{expected_error} +" + ) + } + CairoVersion::Cairo1 => { + let pc_location = entry_point_offset.0 + INNER_CALL_CONTRACT_IN_CALL_CHAIN_OFFSET; + let (expected_pc0, expected_pc1, _, _) = expected_pcs; + format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_contract_hash}, selector: {execute_selector_felt}): +Error at pc=0:767: +1: Error in the called contract (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:{expected_pc0}: +Cairo traceback (most recent call last): +Unknown location (pc=0:{pc_location}) + +2: Error in the called contract (contract address: {address_felt}, class hash: \ + {test_contract_hash}, selector: {invoke_call_chain_selector_felt}): +Error at pc=0:{expected_pc1}: +Cairo traceback (most recent call last): +Unknown location (pc=0:{pc_location}) + +3: {last_call_preamble}: +Execution failed. Failure reason: {expected_error}. +" + ) + } + }; + + assert_eq!(tx_execution_error.to_string(), expected_trace); +} + +// TODO(Arni, 1/5/2024): Cover version 0 declare transaction. +// TODO(Arni, 1/5/2024): Consider version 0 invoke. +#[rstest] +#[case::validate_version_1( + TransactionType::InvokeFunction, + VALIDATE_ENTRY_POINT_NAME, + TransactionVersion::ONE +)] +#[case::validate_version_3( + TransactionType::InvokeFunction, + VALIDATE_ENTRY_POINT_NAME, + TransactionVersion::THREE +)] +#[case::validate_declare_version_1( + TransactionType::Declare, + VALIDATE_DECLARE_ENTRY_POINT_NAME, + TransactionVersion::ONE +)] +#[case::validate_declare_version_2( + TransactionType::Declare, + VALIDATE_DECLARE_ENTRY_POINT_NAME, + TransactionVersion::TWO +)] +#[case::validate_declare_version_3( + TransactionType::Declare, + VALIDATE_DECLARE_ENTRY_POINT_NAME, + TransactionVersion::THREE +)] +#[case::validate_deploy_version_1( + TransactionType::DeployAccount, + VALIDATE_DEPLOY_ENTRY_POINT_NAME, + TransactionVersion::ONE +)] +#[case::validate_deploy_version_3( + TransactionType::DeployAccount, + VALIDATE_DEPLOY_ENTRY_POINT_NAME, + TransactionVersion::THREE +)] +fn test_validate_trace( + #[case] tx_type: TransactionType, + #[case] entry_point_name: &str, + #[case] tx_version: TransactionVersion, + #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, +) { + let create_for_account_testing = &BlockContext::create_for_account_testing(); + let block_context = create_for_account_testing; + let faulty_account = FeatureContract::FaultyAccount(cairo_version); + let mut sender_address = faulty_account.get_instance_address(0); + let class_hash = faulty_account.get_class_hash(); + let state = &mut test_state(&block_context.chain_info, 0, &[(faulty_account, 1)]); + let selector = selector_from_name(entry_point_name).0; + + // Logic failure. + let account_tx = create_account_tx_for_validate_test( + &mut NonceManager::default(), + FaultyAccountTxCreatorArgs { + scenario: INVALID, + tx_type, + tx_version, + sender_address, + class_hash, + ..Default::default() + }, + ); + + if let TransactionType::DeployAccount = tx_type { + // Deploy account uses the actual address as the sender address. + match &account_tx { + AccountTransaction::DeployAccount(tx) => { + sender_address = tx.contract_address; + } + _ => panic!("Expected DeployAccountTransaction type"), + } + } + + let contract_address = *sender_address.0.key(); + + let expected_error = match cairo_version { + CairoVersion::Cairo0 => format!( + "Transaction validation has failed: +0: Error in the called contract (contract address: {contract_address}, class hash: {class_hash}, \ + selector: {selector}): +Error at pc=0:0: +Cairo traceback (most recent call last): +Unknown location (pc=0:0) +Unknown location (pc=0:0) + +An ASSERT_EQ instruction failed: 1 != 0. +" + ), + CairoVersion::Cairo1 => format!( + "Transaction validation has failed: +0: Error in the called contract (contract address: {contract_address}, class hash: {class_hash}, \ + selector: {selector}): +Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid scenario'). +" + ), + }; + + // Clean pc locations from the trace. + let re = Regex::new(r"pc=0:[0-9]+").unwrap(); + let cleaned_expected_error = &re.replace_all(&expected_error, "pc=0:*"); + let actual_error = account_tx.execute(state, block_context, true, true).unwrap_err(); + let actual_error_str = actual_error.to_string(); + let cleaned_actual_error = &re.replace_all(&actual_error_str, "pc=0:*"); + // Compare actual trace to the expected trace (sans pc locations). + assert_eq!(cleaned_actual_error.to_string(), cleaned_expected_error.to_string()); +} + +#[rstest] +/// Tests that hitting an execution error in an account contract constructor outputs the correct +/// traceback (including correct class hash, contract address and constructor entry point selector). +fn test_account_ctor_frame_stack_trace( + block_context: BlockContext, + #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, +) { + let chain_info = &block_context.chain_info; + let faulty_account = FeatureContract::FaultyAccount(cairo_version); + let state = &mut test_state(chain_info, BALANCE, &[(faulty_account, 0)]); + let class_hash = faulty_account.get_class_hash(); + + // Create and execute deploy account transaction that passes validation and fails in the ctor. + let deploy_account_tx = create_account_tx_for_validate_test( + &mut NonceManager::default(), + FaultyAccountTxCreatorArgs { + tx_type: TransactionType::DeployAccount, + scenario: INVALID, + class_hash, + max_fee: Fee(BALANCE), + validate_constructor: true, + ..Default::default() + }, + ); + + // Fund the account so it can afford the deployment. + let deploy_address = match &deploy_account_tx { + AccountTransaction::DeployAccount(deploy_tx) => deploy_tx.contract_address, + _ => unreachable!("deploy_account_tx is a DeployAccount"), + }; + fund_account(chain_info, deploy_address, BALANCE * 2, &mut state.state); + + let expected_selector = selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME).0; + let expected_address = deploy_address.0.key(); + let expected_error = format!( + "Contract constructor execution has failed: +0: Error in the contract class constructor (contract address: {expected_address}, class hash: \ + {class_hash}, selector: {expected_selector}): +" + ) + match cairo_version { + CairoVersion::Cairo0 => { + "Error at pc=0:223: +Cairo traceback (most recent call last): +Unknown location (pc=0:195) +Unknown location (pc=0:179) + +An ASSERT_EQ instruction failed: 1 != 0. +" + } + CairoVersion::Cairo1 => { + "Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid \ + scenario'). +" + } + }; + + // Compare expected and actual error. + let error = deploy_account_tx.execute(state, &block_context, true, true).unwrap_err(); + assert_eq!(error.to_string(), expected_error); +} + +#[rstest] +/// Tests that hitting an execution error in a contract constructor during a deploy syscall outputs +/// the correct traceback (including correct class hash, contract address and constructor entry +/// point selector). +fn test_contract_ctor_frame_stack_trace( + block_context: BlockContext, + #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, +) { + let chain_info = &block_context.chain_info; + let account = FeatureContract::AccountWithoutValidations(cairo_version); + let faulty_ctor = FeatureContract::FaultyAccount(cairo_version); + // Declare both classes, but only "deploy" the dummy account. + let state = &mut test_state(chain_info, BALANCE, &[(account, 1), (faulty_ctor, 0)]); + let account_address = account.get_instance_address(0); + let account_class_hash = account.get_class_hash(); + let faulty_class_hash = faulty_ctor.get_class_hash(); + + let salt = stark_felt!(7_u8); + // Constructor arg: set to true to fail deployment. + let validate_constructor = stark_felt!(FELT_TRUE); + let signature = TransactionSignature(vec![stark_felt!(INVALID)]); + let expected_deployed_address = calculate_contract_address( + ContractAddressSalt(salt), + faulty_class_hash, + &calldata![validate_constructor], + account_address, + ) + .unwrap(); + + // Invoke the deploy_contract function on the dummy account to deploy the faulty contract. + let invoke_deploy_tx = account_invoke_tx(invoke_tx_args! { + max_fee: Fee(BALANCE), + sender_address: account_address, + signature, + calldata: create_calldata( + account_address, + DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME, + &[ + faulty_class_hash.0, + salt, + stark_felt!(1_u8), // Calldata: ctor args length. + validate_constructor, + ] + ), + version: TransactionVersion::ONE, + nonce: Nonce(stark_felt!(0_u8)), + }); + + // Construct expected output. + let execute_selector = selector_from_name(EXECUTE_ENTRY_POINT_NAME); + let deploy_contract_selector = selector_from_name(DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME); + let ctor_selector = selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME); + let account_address_felt = *account_address.0.key(); + let faulty_class_hash = faulty_ctor.get_class_hash(); + let expected_address = expected_deployed_address.0.key(); + + let (frame_0, frame_1, frame_2) = ( + format!( + "Transaction execution has failed: +0: Error in the called contract (contract address: {account_address_felt}, class hash: \ + {account_class_hash}, selector: {}):", + execute_selector.0 + ), + format!( + "1: Error in the called contract (contract address: {account_address_felt}, class \ + hash: {account_class_hash}, selector: {}):", + deploy_contract_selector.0 + ), + format!( + "2: Error in the contract class constructor (contract address: {expected_address}, \ + class hash: {faulty_class_hash}, selector: {}):", + ctor_selector.0 + ), + ); + let (execute_offset, deploy_offset, ctor_offset) = ( + account.get_entry_point_offset(execute_selector).0, + account.get_entry_point_offset(deploy_contract_selector).0, + faulty_ctor.get_ctor_offset(Some(ctor_selector)).0, + ); + + let expected_error = match cairo_version { + CairoVersion::Cairo0 => { + format!( + "{frame_0} +Error at pc=0:7: +Cairo traceback (most recent call last): +Unknown location (pc=0:{}) +Unknown location (pc=0:{}) + +{frame_1} +Error at pc=0:20: +Cairo traceback (most recent call last): +Unknown location (pc=0:{}) +Unknown location (pc=0:{}) + +{frame_2} +Error at pc=0:223: +Cairo traceback (most recent call last): +Unknown location (pc=0:{}) +Unknown location (pc=0:{}) + +An ASSERT_EQ instruction failed: 1 != 0. +", + execute_offset + 18, + execute_offset - 8, + deploy_offset + 14, + deploy_offset - 12, + ctor_offset + 7, + ctor_offset - 9 + ) + } + CairoVersion::Cairo1 => { + // TODO(Dori, 1/1/2025): Get lowest level PC locations from Cairo1 errors (ctor offset + // does not appear in the trace). + format!( + "{frame_0} +Error at pc=0:{}: +{frame_1} +Error at pc=0:{}: +{frame_2} +Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid scenario'). +", + execute_offset + 205, + deploy_offset + 194 + ) + } + }; + + // Compare expected and actual error. + let error = + invoke_deploy_tx.execute(state, &block_context, true, true).unwrap().revert_error.unwrap(); + assert_eq!(error.to_string(), expected_error); +} diff --git a/crates/blockifier/src/execution/syscalls/syscalls_test.rs b/crates/blockifier/src/execution/syscalls/syscalls_test.rs index 5768627683f..7365fab9555 100644 --- a/crates/blockifier/src/execution/syscalls/syscalls_test.rs +++ b/crates/blockifier/src/execution/syscalls/syscalls_test.rs @@ -299,7 +299,7 @@ fn test_keccak() { assert_eq!( entry_point_call.execute_directly(&mut state).unwrap().execution, - CallExecution { gas_consumed: 256250, ..CallExecution::from_retdata(retdata![]) } + CallExecution { gas_consumed: 256950, ..CallExecution::from_retdata(retdata![]) } ); } @@ -777,7 +777,7 @@ fn test_secp256k1() { assert_eq!( entry_point_call.execute_directly(&mut state).unwrap().execution, - CallExecution { gas_consumed: 17033910_u64, ..Default::default() } + CallExecution { gas_consumed: 17035610_u64, ..Default::default() } ); } @@ -796,7 +796,7 @@ fn test_secp256r1() { assert_eq!( entry_point_call.execute_directly(&mut state).unwrap().execution, - CallExecution { gas_consumed: 27582260_u64, ..Default::default() } + CallExecution { gas_consumed: 27582560_u64, ..Default::default() } ); } diff --git a/crates/blockifier/src/fee/fee_checks.rs b/crates/blockifier/src/fee/fee_checks.rs index 26a962ae73e..f155db8ec67 100644 --- a/crates/blockifier/src/fee/fee_checks.rs +++ b/crates/blockifier/src/fee/fee_checks.rs @@ -19,11 +19,11 @@ use crate::transaction::objects::{ pub enum FeeCheckError { #[error("Insufficient max L1 gas: max amount: {max_amount}, actual used: {actual_amount}.")] MaxL1GasAmountExceeded { max_amount: u128, actual_amount: u128 }, - #[error("Insufficient max fee: max fee: {max_fee:?}, actual fee: {actual_fee:?}")] + #[error("Insufficient max fee: max fee: {}, actual fee: {}.", max_fee.0, actual_fee.0)] MaxFeeExceeded { max_fee: Fee, actual_fee: Fee }, #[error( - "Insufficient fee token balance. Fee: {fee:?}, balance: low/high \ - {balance_low:?}/{balance_high:?}." + "Insufficient fee token balance. Fee: {}, balance: low/high \ + {balance_low}/{balance_high}.", fee.0 )] InsufficientFeeTokenBalance { fee: Fee, balance_low: StarkFelt, balance_high: StarkFelt }, } diff --git a/crates/blockifier/src/fee/fee_utils.rs b/crates/blockifier/src/fee/fee_utils.rs index 6f43f6f72da..fa742535381 100644 --- a/crates/blockifier/src/fee/fee_utils.rs +++ b/crates/blockifier/src/fee/fee_utils.rs @@ -2,9 +2,12 @@ use std::collections::HashSet; use cairo_vm::vm::runners::cairo_runner::ExecutionResources; use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; use starknet_api::transaction::Fee; +use crate::abi::abi_utils::get_fee_token_var_address; use crate::abi::constants; +use crate::abi::sierra_types::next_storage_key; use crate::blockifier::block::BlockInfo; use crate::context::{BlockContext, TransactionContext}; use crate::state::state_api::StateReader; @@ -145,3 +148,11 @@ pub fn verify_can_pay_committed_bounds( }) } } + +pub fn get_sequencer_balance_keys(block_context: &BlockContext) -> (StorageKey, StorageKey) { + let sequencer_address = block_context.block_info.sequencer_address; + let sequencer_balance_key_low = get_fee_token_var_address(sequencer_address); + let sequencer_balance_key_high = next_storage_key(&sequencer_balance_key_low) + .expect("Cannot get sequencer balance high key."); + (sequencer_balance_key_low, sequencer_balance_key_high) +} diff --git a/crates/blockifier/src/state/cached_state.rs b/crates/blockifier/src/state/cached_state.rs index 9a6ffab8587..09cc6491263 100644 --- a/crates/blockifier/src/state/cached_state.rs +++ b/crates/blockifier/src/state/cached_state.rs @@ -10,7 +10,7 @@ use starknet_api::state::StorageKey; use crate::abi::abi_utils::get_fee_token_var_address; use crate::execution::contract_class::ContractClass; use crate::state::errors::StateError; -use crate::state::state_api::{State, StateReader, StateResult}; +use crate::state::state_api::{State, StateReader, StateResult, UpdatableState}; use crate::utils::{strict_subtract_mappings, subtract_mappings}; #[cfg(test)] @@ -44,33 +44,22 @@ impl CachedState { } } - /// Creates a transactional instance from the given cached state. - /// It allows performing buffered modifying actions on the given state, which - /// will either all happen (will be committed) or none of them (will be discarded). - pub fn create_transactional(state: &mut CachedState) -> TransactionalState<'_, S> { - CachedState::new(MutRefState::new(state)) + /// Returns the state diff resulting from the performed writes, with respect to the parent + /// state. + pub fn to_state_diff(&mut self) -> StateResult { + self.update_initial_values_of_write_only_access()?; + Ok(self.cache.borrow().to_state_diff()) } - /// Returns the storage changes done through this state. - /// For each contract instance (address) we have three attributes: (class hash, nonce, storage - /// root); the state updates correspond to them. + // TODO(Yoni, 1/8/2024): remove this function. + /// Returns the state changes made on this state. pub fn get_actual_state_changes(&mut self) -> StateResult { - self.update_initial_values_of_write_only_access()?; - let cache = self.cache.borrow(); - - Ok(StateChanges { - storage_updates: cache.get_storage_updates(), - nonce_updates: cache.get_nonce_updates(), - // Class hash updates (deployed contracts + replace_class syscall). - class_hash_updates: cache.get_class_hash_updates(), - // Compiled class hash updates (declare Cairo 1 contract). - compiled_class_hash_updates: cache.get_compiled_class_hash_updates(), - }) + Ok(self.to_state_diff()?.into()) } - pub fn update_cache(&mut self, write_updates: StateMaps) { + pub fn update_cache(&mut self, write_updates: &StateMaps) { let mut cache = self.cache.borrow_mut(); - cache.writes.extend(&write_updates); + cache.writes.extend(write_updates); } pub fn update_contract_class_cache( @@ -89,13 +78,18 @@ impl CachedState { /// Updates cache with initial cell values for write-only access. /// If written values match the original, the cell is unchanged and not counted as a /// storage-change for fee calculation. - /// Same for class hash and nonce writes. - // TODO(Noa, 30/07/23): Consider adding DB getters in bulk (via a DB read transaction). + /// Note: in valid flows, all other read mappings must be filled at this point: + /// * Nonce: read previous before incrementing. + /// * Class hash: Deploy: verify the address is not occupied; Replace class: verify the + /// contract is deployed before running any code. + /// * Compiled class hash: verify the class is not declared through + /// `get_compiled_contract_class`. + /// + /// TODO(Noa, 30/07/23): Consider adding DB getters in bulk (via a DB read transaction). fn update_initial_values_of_write_only_access(&mut self) -> StateResult<()> { let cache = &mut *self.cache.borrow_mut(); - // Eliminate storage writes that are identical to the initial value (no change). Assumes - // that `set_storage_at` does not affect the state field. + // Eliminate storage writes that are identical to the initial value (no change). for contract_storage_key in cache.writes.storage.keys() { if !cache.initial_reads.storage.contains_key(contract_storage_key) { // First access to this cell was write; cache initial value. @@ -105,49 +99,21 @@ impl CachedState { ); } } - - for contract_address in cache.writes.class_hashes.keys() { - if !cache.initial_reads.class_hashes.contains_key(contract_address) { - // First access to this cell was write; cache initial value. - cache - .initial_reads - .class_hashes - .insert(*contract_address, self.state.get_class_hash_at(*contract_address)?); - } - } - - for contract_address in cache.writes.nonces.keys() { - if !cache.initial_reads.nonces.contains_key(contract_address) { - // First access to this cell was write; cache initial value. - cache - .initial_reads - .nonces - .insert(*contract_address, self.state.get_nonce_at(*contract_address)?); - } - } - Ok(()) } +} - pub fn to_state_diff(&mut self) -> CommitmentStateDiff { - type StorageDiff = IndexMap>; - - // TODO(Gilad): Consider returning an error here, would require changing the API though. - self.update_initial_values_of_write_only_access() - .unwrap_or_else(|_| panic!("Cannot convert stateDiff to CommitmentStateDiff.")); - - let state_cache = self.cache.borrow(); - let class_hash_updates = state_cache.get_class_hash_updates(); - let storage_diffs = state_cache.get_storage_updates(); - let nonces = state_cache.get_nonce_updates(); - let declared_classes = state_cache.writes.compiled_class_hashes.clone(); - - CommitmentStateDiff { - address_to_class_hash: IndexMap::from_iter(class_hash_updates), - storage_updates: StorageDiff::from(StorageView(storage_diffs)), - class_hash_to_compiled_class_hash: IndexMap::from_iter(declared_classes), - address_to_nonce: IndexMap::from_iter(nonces), - } +impl UpdatableState for CachedState { + fn apply_writes( + &mut self, + writes: &StateMaps, + class_hash_to_class: &ContractClassMapping, + visited_pcs: &HashMap>, + ) { + self.update_cache(writes); + // TODO(OriF,15/5/24): Reconsider the clone. + self.update_contract_class_cache(class_hash_to_class.clone()); + self.update_visited_pcs_cache(visited_pcs); } } @@ -216,6 +182,10 @@ impl StateReader for CachedState { match self.state.get_compiled_contract_class(class_hash) { Err(StateError::UndeclaredClassHash(class_hash)) => { cache.set_declared_contract_initial_values(class_hash, false); + cache.set_compiled_class_hash_initial_value( + class_hash, + CompiledClassHash(StarkFelt::ZERO), + ); Err(StateError::UndeclaredClassHash(class_hash))?; } Err(error) => Err(error)?, @@ -346,6 +316,7 @@ impl From for IndexMap, @@ -363,6 +334,26 @@ impl StateMaps { self.compiled_class_hashes.extend(&other.compiled_class_hashes); self.declared_contracts.extend(&other.declared_contracts) } + + /// Subtracts other's mappings from self. + /// Assumes (and enforces) other's keys contains self's. + pub fn diff(&self, other: &Self) -> Self { + Self { + nonces: strict_subtract_mappings(&self.nonces, &other.nonces), + class_hashes: strict_subtract_mappings(&self.class_hashes, &other.class_hashes), + storage: strict_subtract_mappings(&self.storage, &other.storage), + compiled_class_hashes: strict_subtract_mappings( + &self.compiled_class_hashes, + &other.compiled_class_hashes, + ), + // TODO(Yoni, 1/8/2024): consider forbid redeclaration of Cairo 0, to be able to use + // strict subtraction here, for completeness. + declared_contracts: subtract_mappings( + &self.declared_contracts, + &other.declared_contracts, + ), + } + } } /// Caches read and write requests. /// The tracked changes are needed for block state commitment. @@ -378,6 +369,12 @@ pub struct StateCache { } impl StateCache { + /// Returns the state diff resulting from the performed writes, with respect to the initial + /// reads. Assumes (and enforces) all initial reads are cached. + pub fn to_state_diff(&self) -> StateMaps { + self.writes.diff(&self.initial_reads) + } + fn declare_contract(&mut self, class_hash: ClassHash) { self.writes.declared_contracts.insert(class_hash, true); } @@ -474,44 +471,20 @@ impl StateCache { ) { self.writes.compiled_class_hashes.insert(class_hash, compiled_class_hash); } - - fn get_storage_updates(&self) -> HashMap { - strict_subtract_mappings(&self.writes.storage, &self.initial_reads.storage) - } - - fn get_class_hash_updates(&self) -> HashMap { - strict_subtract_mappings(&self.writes.class_hashes, &self.initial_reads.class_hashes) - } - - fn get_nonce_updates(&self) -> HashMap { - strict_subtract_mappings(&self.writes.nonces, &self.initial_reads.nonces) - } - - fn get_compiled_class_hash_updates(&self) -> HashMap { - // This is not a strict subtraction, as Papyrus does not support the - // `get_compiled_class_hash` method. When declaring a Cairo 1 class we update the - // writes mapping but cannot update the reads mapping. As a result, the compiled - // class hash writes keys are not a subset of compiled class hash initial values keys. - - subtract_mappings( - &self.writes.compiled_class_hashes, - &self.initial_reads.compiled_class_hashes, - ) - } } /// Wraps a mutable reference to a `State` object, exposing its API. /// Used to pass ownership to a `CachedState`. -pub struct MutRefState<'a, S: State + ?Sized>(&'a mut S); +pub struct MutRefState<'a, U: UpdatableState + ?Sized>(&'a mut U); -impl<'a, S: State + ?Sized> MutRefState<'a, S> { - pub fn new(state: &'a mut S) -> Self { +impl<'a, U: UpdatableState + ?Sized> MutRefState<'a, U> { + pub fn new(state: &'a mut U) -> Self { Self(state) } } /// Proxies inner object to expose `State` functionality. -impl<'a, S: State + ?Sized> StateReader for MutRefState<'a, S> { +impl<'a, U: UpdatableState + ?Sized> StateReader for MutRefState<'a, U> { fn get_storage_at( &self, contract_address: ContractAddress, @@ -537,25 +510,38 @@ impl<'a, S: State + ?Sized> StateReader for MutRefState<'a, S> { } } -pub type TransactionalState<'a, S> = CachedState>>; +pub type TransactionalState<'a, U> = CachedState>; /// Adds the ability to perform a transactional execution. -impl<'a, S: StateReader> TransactionalState<'a, S> { +impl<'a, U: UpdatableState> TransactionalState<'a, U> { + /// Creates a transactional instance from the given updatable state. + /// It allows performing buffered modifying actions on the given state, which + /// will either all happen (will be updated in the state and committed) + /// or none of them (will be discarded). + pub fn create_transactional(state: &mut U) -> TransactionalState<'_, U> { + CachedState::new(MutRefState::new(state)) + } + /// Commits changes in the child (wrapping) state to its parent. pub fn commit(self) { let state = self.state.0; let child_cache = self.cache.into_inner(); - state.update_cache(child_cache.writes); - state.update_contract_class_cache(self.class_hash_to_class.into_inner()); - state.update_visited_pcs_cache(&self.visited_pcs); + state.apply_writes( + &child_cache.writes, + &self.class_hash_to_class.into_inner(), + &self.visited_pcs, + ) } /// Drops `self`. pub fn abort(self) {} } +type StorageDiff = IndexMap>; + /// Holds uncommitted changes induced on Starknet contracts. -#[derive(Clone, Debug, Eq, PartialEq)] +#[cfg_attr(any(feature = "testing", test), derive(Clone))] +#[derive(Debug, Eq, PartialEq)] pub struct CommitmentStateDiff { // Contract instance attributes (per address). pub address_to_class_hash: IndexMap, @@ -566,12 +552,24 @@ pub struct CommitmentStateDiff { pub class_hash_to_compiled_class_hash: IndexMap, } +impl From for CommitmentStateDiff { + fn from(diff: StateMaps) -> Self { + Self { + address_to_class_hash: IndexMap::from_iter(diff.class_hashes), + storage_updates: StorageDiff::from(StorageView(diff.storage)), + class_hash_to_compiled_class_hash: IndexMap::from_iter(diff.compiled_class_hashes), + address_to_nonce: IndexMap::from_iter(diff.nonces), + } + } +} + /// Used to track the state diff size, which is determined by the number of new keys. /// Also, can be used to accuratly measure the contribution of a single (say, transactional) /// state to a cumulative state diff - provides set-like functionallities for this porpuse. /// /// Note: Cancelling writes (0 -> 1 -> 0) are neglected here. -#[derive(Clone, Debug, Default, Eq, PartialEq)] +#[cfg_attr(any(feature = "testing", test), derive(Clone))] +#[derive(Debug, Default, Eq, PartialEq)] pub struct StateChangesKeys { nonce_keys: HashSet, class_hash_keys: HashSet, @@ -634,13 +632,9 @@ impl StateChangesKeys { } /// Holds the state changes. -#[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct StateChanges { - pub storage_updates: HashMap, - pub nonce_updates: HashMap, - pub class_hash_updates: HashMap, - pub compiled_class_hash_updates: HashMap, -} +#[cfg_attr(any(feature = "testing", test), derive(Clone))] +#[derive(Debug, Default, Eq, PartialEq)] +pub struct StateChanges(pub StateMaps); impl StateChanges { /// Merges the given state changes into a single one. Note that the order of the state changes @@ -648,12 +642,7 @@ impl StateChanges { pub fn merge(state_changes: Vec) -> Self { let mut merged_state_changes = Self::default(); for state_change in state_changes { - merged_state_changes.storage_updates.extend(state_change.storage_updates); - merged_state_changes.nonce_updates.extend(state_change.nonce_updates); - merged_state_changes.class_hash_updates.extend(state_change.class_hash_updates); - merged_state_changes - .compiled_class_hash_updates - .extend(state_change.compiled_class_hash_updates); + merged_state_changes.0.extend(&state_change.0); } merged_state_changes @@ -662,11 +651,11 @@ impl StateChanges { pub fn get_modified_contracts(&self) -> HashSet { // Storage updates. let mut modified_contracts: HashSet = - self.storage_updates.keys().map(|address_key_pair| address_key_pair.0).collect(); + self.0.storage.keys().map(|address_key_pair| address_key_pair.0).collect(); // Nonce updates. - modified_contracts.extend(self.nonce_updates.keys()); + modified_contracts.extend(self.0.nonces.keys()); // Class hash updates (deployed contracts + replace_class syscall). - modified_contracts.extend(self.class_hash_updates.keys()); + modified_contracts.extend(self.0.class_hashes.keys()); modified_contracts } @@ -683,10 +672,10 @@ impl StateChanges { // fee transfer. The fee transfer is going to update the balance of the sequencer // and the balance of the sender contract, but we don't charge the sender for the // sequencer balance change as it is amortized across the block. - let mut n_storage_updates = self.storage_updates.len(); + let mut n_storage_updates = self.0.storage.len(); if let Some(sender_address) = sender_address { let sender_balance_key = get_fee_token_var_address(sender_address); - if !self.storage_updates.contains_key(&(fee_token_address, sender_balance_key)) { + if !self.0.storage.contains_key(&(fee_token_address, sender_balance_key)) { n_storage_updates += 1; } } @@ -697,8 +686,8 @@ impl StateChanges { StateChangesCount { n_storage_updates, - n_class_hash_updates: self.class_hash_updates.len(), - n_compiled_class_hash_updates: self.compiled_class_hash_updates.len(), + n_class_hash_updates: self.0.class_hashes.len(), + n_compiled_class_hash_updates: self.0.compiled_class_hashes.len(), n_modified_contracts: modified_contracts.len(), } } @@ -706,14 +695,20 @@ impl StateChanges { pub fn into_keys(self) -> StateChangesKeys { StateChangesKeys { modified_contracts: self.get_modified_contracts(), - nonce_keys: self.nonce_updates.into_keys().collect(), - class_hash_keys: self.class_hash_updates.into_keys().collect(), - storage_keys: self.storage_updates.into_keys().collect(), - compiled_class_hash_keys: self.compiled_class_hash_updates.into_keys().collect(), + nonce_keys: self.0.nonces.into_keys().collect(), + class_hash_keys: self.0.class_hashes.into_keys().collect(), + storage_keys: self.0.storage.into_keys().collect(), + compiled_class_hash_keys: self.0.compiled_class_hashes.into_keys().collect(), } } } +impl From for StateChanges { + fn from(state_maps: StateMaps) -> Self { + Self(state_maps) + } +} + /// Holds the number of state changes. #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] pub struct StateChangesCount { diff --git a/crates/blockifier/src/state/cached_state_test.rs b/crates/blockifier/src/state/cached_state_test.rs index 8854b4f58ad..4fa45f204ec 100644 --- a/crates/blockifier/src/state/cached_state_test.rs +++ b/crates/blockifier/src/state/cached_state_test.rs @@ -259,8 +259,10 @@ fn cached_state_state_diff_conversion() { ); // Declare a new class. - let class_hash = FeatureContract::Empty(CairoVersion::Cairo0).get_class_hash(); // Some unused class hash. + let class_hash = FeatureContract::Empty(CairoVersion::Cairo0).get_class_hash(); let compiled_class_hash = compiled_class_hash!(1_u8); + // Cache the initial read value, as in regular declare flow. + state.get_compiled_contract_class(class_hash).unwrap_err(); state.set_compiled_class_hash(class_hash, compiled_class_hash).unwrap(); // Write the initial value using key contract_address1. @@ -271,6 +273,9 @@ fn cached_state_state_diff_conversion() { state.set_storage_at(contract_address2, key_y, new_value).unwrap(); assert!(state.increment_nonce(contract_address2).is_ok()); let new_class_hash = class_hash!("0x11111111"); + + // Cache the initial read value, as in regular deploy flow. + state.get_class_hash_at(contract_address2).unwrap(); assert!(state.set_class_hash_at(contract_address2, new_class_hash).is_ok()); // Only changes to contract_address2 should be shown, since contract_address_0 wasn't changed @@ -282,7 +287,7 @@ fn cached_state_state_diff_conversion() { address_to_nonce: IndexMap::from_iter([(contract_address2, nonce!(1_u64))]), }; - assert_eq!(expected_state_diff, state.to_state_diff()); + assert_eq!(expected_state_diff, state.to_state_diff().unwrap().into()); } fn create_state_changes_for_test( @@ -297,9 +302,15 @@ fn create_state_changes_for_test( let key = storage_key!("0x10"); let storage_val: StarkFelt = stark_felt!("0x1"); + // Fill the initial read value, as in regular flow. + state.get_class_hash_at(contract_address).unwrap(); state.set_class_hash_at(contract_address, class_hash).unwrap(); + state.set_storage_at(contract_address, key, storage_val).unwrap(); state.increment_nonce(contract_address2).unwrap(); + + // Fill the initial read value, as in regular flow. + state.get_compiled_contract_class(class_hash).unwrap_err(); state.set_compiled_class_hash(class_hash, compiled_class_hash).unwrap(); // Assign the existing value to the storage (this shouldn't be considered a change). @@ -343,7 +354,7 @@ fn test_state_changes_merge( // Create a transactional state containing the `create_state_changes_for_test` logic, get the // state changes and then commit. let mut state: CachedState = CachedState::default(); - let mut transactional_state = CachedState::create_transactional(&mut state); + let mut transactional_state = TransactionalState::create_transactional(&mut state); let block_context = BlockContext::create_for_testing(); let fee_token_address = block_context.chain_info.fee_token_addresses.eth_fee_token_address; let state_changes1 = @@ -352,7 +363,7 @@ fn test_state_changes_merge( // After performing `commit`, the transactional state is moved (into state). We need to create // a new transactional state that wraps `state` to continue. - let mut transactional_state = CachedState::create_transactional(&mut state); + let mut transactional_state = TransactionalState::create_transactional(&mut state); // Make sure that `get_actual_state_changes` on a newly created transactional state returns null // state changes and that merging null state changes with non-null state changes results in the // non-null state changes, no matter the order. @@ -368,7 +379,7 @@ fn test_state_changes_merge( ); // Get the storage updates addresses and keys from the state_changes1, to overwrite. - let mut storage_updates_keys = state_changes1.storage_updates.keys(); + let mut storage_updates_keys = state_changes1.0.storage.keys(); let &(contract_address, storage_key) = storage_updates_keys .find(|(contract_address, _)| contract_address == &contract_address!(CONTRACT_ADDRESS)) .unwrap(); @@ -440,19 +451,20 @@ fn test_cache_get_write_keys() { let class_hash0 = class_hash!("0x300"); - let state_changes = StateChanges { - nonce_updates: HashMap::from([(contract_address0, Nonce(some_felt))]), - class_hash_updates: HashMap::from([ + let state_changes = StateChanges(StateMaps { + nonces: HashMap::from([(contract_address0, Nonce(some_felt))]), + class_hashes: HashMap::from([ (contract_address1, some_class_hash), (contract_address2, some_class_hash), ]), - storage_updates: HashMap::from([ + storage: HashMap::from([ ((contract_address1, storage_key!("0x300")), some_felt), ((contract_address1, storage_key!("0x600")), some_felt), ((contract_address3, storage_key!("0x600")), some_felt), ]), - compiled_class_hash_updates: HashMap::from([(class_hash0, compiled_class_hash!("0x3"))]), - }; + compiled_class_hashes: HashMap::from([(class_hash0, compiled_class_hash!("0x3"))]), + declared_contracts: HashMap::default(), + }); let expected_keys = StateChangesKeys { nonce_keys: HashSet::from([contract_address0]), diff --git a/crates/blockifier/src/state/state_api.rs b/crates/blockifier/src/state/state_api.rs index 9745ca62f14..23e6ba81179 100644 --- a/crates/blockifier/src/state/state_api.rs +++ b/crates/blockifier/src/state/state_api.rs @@ -1,9 +1,10 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; +use super::cached_state::{ContractClassMapping, StateMaps}; use crate::abi::abi_utils::get_fee_token_var_address; use crate::abi::sierra_types::next_storage_key; use crate::execution::contract_class::ContractClass; @@ -108,3 +109,13 @@ pub trait State: StateReader { // entry points do not affect the final set of PCs. fn add_visited_pcs(&mut self, class_hash: ClassHash, pcs: &HashSet); } + +/// A class defining the API for updating a state with transactions writes. +pub trait UpdatableState: StateReader { + fn apply_writes( + &mut self, + writes: &StateMaps, + class_hash_to_class: &ContractClassMapping, + visited_pcs: &HashMap>, + ); +} diff --git a/crates/blockifier/src/test_utils.rs b/crates/blockifier/src/test_utils.rs index 11944ad1986..dd12531fa8f 100644 --- a/crates/blockifier/src/test_utils.rs +++ b/crates/blockifier/src/test_utils.rs @@ -75,6 +75,13 @@ impl CairoVersion { _ => panic!("Transaction version {:?} is not supported.", tx_version), } } + + pub fn other(&self) -> Self { + match self { + Self::Cairo0 => Self::Cairo1, + Self::Cairo1 => Self::Cairo0, + } + } } // Storage keys. diff --git a/crates/blockifier/src/test_utils/contracts.rs b/crates/blockifier/src/test_utils/contracts.rs index 5b584b7175e..78df77275c0 100644 --- a/crates/blockifier/src/test_utils/contracts.rs +++ b/crates/blockifier/src/test_utils/contracts.rs @@ -1,8 +1,22 @@ -use starknet_api::core::{ClassHash, ContractAddress, PatriciaKey}; -use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; -use starknet_api::hash::StarkHash; -use starknet_api::{class_hash, contract_address, patricia_key}; +use starknet_api::core::{ + ClassHash, + CompiledClassHash, + ContractAddress, + EntryPointSelector, + PatriciaKey, +}; +use starknet_api::deprecated_contract_class::{ + ContractClass as DeprecatedContractClass, + EntryPointOffset, + EntryPointType, +}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::{class_hash, contract_address, patricia_key, stark_felt}; +use strum::IntoEnumIterator; +use strum_macros::EnumIter; +use crate::abi::abi_utils::selector_from_name; +use crate::abi::constants::CONSTRUCTOR_ENTRY_POINT_NAME; use crate::execution::contract_class::{ContractClass, ContractClassV0, ContractClassV1}; use crate::test_utils::{get_raw_contract_class, CairoVersion}; @@ -54,7 +68,7 @@ const ERC20_CONTRACT_PATH: &str = /// Enum representing all feature contracts. /// The contracts that are implemented in both Cairo versions include a version field. -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, EnumIter)] pub enum FeatureContract { AccountWithLongValidate(CairoVersion), AccountWithoutValidations(CairoVersion), @@ -79,6 +93,17 @@ impl FeatureContract { } } + fn has_two_versions(&self) -> bool { + match self { + Self::AccountWithLongValidate(_) + | Self::AccountWithoutValidations(_) + | Self::Empty(_) + | Self::FaultyAccount(_) + | Self::TestContract(_) => true, + Self::SecurityTests | Self::ERC20 | Self::LegacyTestContract => false, + } + } + fn get_cairo_version_bit(&self) -> u32 { match self.cairo_version() { CairoVersion::Cairo0 => 0, @@ -101,7 +126,7 @@ impl FeatureContract { } } - fn get_compiled_path(&self) -> String { + pub fn get_compiled_path(&self) -> String { let cairo_version = self.cairo_version(); let contract_name = match self { Self::AccountWithLongValidate(_) => ACCOUNT_LONG_VALIDATE_NAME, @@ -115,7 +140,7 @@ impl FeatureContract { Self::ERC20 => return ERC20_CONTRACT_PATH.into(), }; format!( - "./feature_contracts/cairo{}/compiled/{}{}.json", + "feature_contracts/cairo{}/compiled/{}{}.json", match cairo_version { CairoVersion::Cairo0 => "0", CairoVersion::Cairo1 => "1", @@ -145,6 +170,13 @@ impl FeatureContract { class_hash!(self.get_integer_base()) } + pub fn get_compiled_class_hash(&self) -> CompiledClassHash { + match self.cairo_version() { + CairoVersion::Cairo0 => CompiledClassHash(StarkFelt::ZERO), + CairoVersion::Cairo1 => CompiledClassHash(stark_felt!(self.get_integer_base())), + } + } + /// Returns the address of the instance with the given instance ID. pub fn get_instance_address(&self, instance_id: u16) -> ContractAddress { let instance_id_as_u32: u32 = instance_id.into(); @@ -176,4 +208,66 @@ impl FeatureContract { pub fn get_raw_class(&self) -> String { get_raw_contract_class(&self.get_compiled_path()) } + + /// Fetch PC locations from the compiled contract to compute the expected PC locations in the + /// traceback. Computation is not robust, but as long as the cairo function itself is not + /// edited, this computation should be stable. + fn get_offset( + &self, + entry_point_selector: EntryPointSelector, + entry_point_type: EntryPointType, + ) -> EntryPointOffset { + match self.get_class() { + ContractClass::V0(class) => { + class + .entry_points_by_type + .get(&entry_point_type) + .unwrap() + .iter() + .find(|ep| ep.selector == entry_point_selector) + .unwrap() + .offset + } + ContractClass::V1(class) => { + class + .entry_points_by_type + .get(&entry_point_type) + .unwrap() + .iter() + .find(|ep| ep.selector == entry_point_selector) + .unwrap() + .offset + } + } + } + + pub fn get_entry_point_offset( + &self, + entry_point_selector: EntryPointSelector, + ) -> EntryPointOffset { + self.get_offset(entry_point_selector, EntryPointType::External) + } + + pub fn get_ctor_offset( + &self, + entry_point_selector: Option, + ) -> EntryPointOffset { + let selector = + entry_point_selector.unwrap_or(selector_from_name(CONSTRUCTOR_ENTRY_POINT_NAME)); + self.get_offset(selector, EntryPointType::Constructor) + } + + pub fn all_contracts() -> impl Iterator { + // EnumIter iterates over all variants with Default::default() as the cairo + // version. + Self::iter().flat_map(|contract| { + if contract.has_two_versions() { + let mut other_contract = contract; + other_contract.set_cairo_version(contract.cairo_version().other()); + vec![contract, other_contract].into_iter() + } else { + vec![contract].into_iter() + } + }) + } } diff --git a/crates/blockifier/src/test_utils/dict_state_reader.rs b/crates/blockifier/src/test_utils/dict_state_reader.rs index 0337403c099..2f700da6da1 100644 --- a/crates/blockifier/src/test_utils/dict_state_reader.rs +++ b/crates/blockifier/src/test_utils/dict_state_reader.rs @@ -10,7 +10,7 @@ use crate::state::errors::StateError; use crate::state::state_api::{StateReader, StateResult}; /// A simple implementation of `StateReader` using `HashMap`s as storage. -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct DictStateReader { pub storage_view: HashMap, pub address_to_nonce: HashMap, diff --git a/crates/blockifier/src/transaction/account_transaction.rs b/crates/blockifier/src/transaction/account_transaction.rs index b0a2d480882..08bc853aa63 100644 --- a/crates/blockifier/src/transaction/account_transaction.rs +++ b/crates/blockifier/src/transaction/account_transaction.rs @@ -7,19 +7,22 @@ use starknet_api::deprecated_contract_class::EntryPointType; use starknet_api::hash::StarkFelt; use starknet_api::transaction::{Calldata, Fee, ResourceBounds, TransactionVersion}; -use crate::abi::abi_utils::{get_fee_token_var_address, selector_from_name}; -use crate::abi::sierra_types::next_storage_key; +use crate::abi::abi_utils::selector_from_name; use crate::context::{BlockContext, TransactionContext}; use crate::execution::call_info::{CallInfo, Retdata}; use crate::execution::contract_class::ContractClass; use crate::execution::entry_point::{CallEntryPoint, CallType, EntryPointExecutionContext}; use crate::fee::actual_cost::TransactionReceipt; use crate::fee::fee_checks::{FeeCheckReportFields, PostExecutionReport}; -use crate::fee::fee_utils::{get_fee_by_gas_vector, verify_can_pay_committed_bounds}; +use crate::fee::fee_utils::{ + get_fee_by_gas_vector, + get_sequencer_balance_keys, + verify_can_pay_committed_bounds, +}; use crate::fee::gas_usage::{compute_discounted_gas_from_gas_vector, estimate_minimal_gas_vector}; use crate::retdata; -use crate::state::cached_state::{CachedState, StateChanges, TransactionalState}; -use crate::state::state_api::{State, StateReader}; +use crate::state::cached_state::{StateChanges, TransactionalState}; +use crate::state::state_api::{State, StateReader, UpdatableState}; use crate::transaction::constants; use crate::transaction::errors::{ TransactionExecutionError, @@ -312,9 +315,9 @@ impl AccountTransaction { Ok(()) } - fn handle_fee( + fn handle_fee( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, tx_context: Arc, actual_fee: Fee, charge_fee: bool, @@ -381,18 +384,16 @@ impl AccountTransaction { /// manipulates the state to avoid that part. /// Note: the returned transfer call info is partial, and should be completed at the commit /// stage, as well as the actual sequencer balance. - fn concurrency_execute_fee_transfer( - state: &mut TransactionalState<'_, S>, + fn concurrency_execute_fee_transfer( + state: &mut TransactionalState<'_, U>, tx_context: Arc, actual_fee: Fee, ) -> TransactionExecutionResult { let TransactionContext { block_context, tx_info } = tx_context.as_ref(); let fee_address = block_context.chain_info.fee_token_address(&tx_info.fee_type()); - let sequencer_address = block_context.block_info.sequencer_address; - let sequencer_balance_key_low = get_fee_token_var_address(sequencer_address); - let sequencer_balance_key_high = next_storage_key(&sequencer_balance_key_low) - .expect("Cannot get sequencer balance high key."); - let mut transfer_state = CachedState::create_transactional(state); + let (sequencer_balance_key_low, sequencer_balance_key_high) = + get_sequencer_balance_keys(block_context); + let mut transfer_state = TransactionalState::create_transactional(state); // Set the initial sequencer balance to avoid tarnishing the read-set of the transaction. let cache = transfer_state.cache.get_mut(); @@ -424,9 +425,9 @@ impl AccountTransaction { } } - fn run_non_revertible( + fn run_non_revertible( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, tx_context: Arc, remaining_gas: &mut u64, validate: bool, @@ -487,9 +488,9 @@ impl AccountTransaction { } } - fn run_revertible( + fn run_revertible( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, tx_context: Arc, remaining_gas: &mut u64, validate: bool, @@ -521,7 +522,7 @@ impl AccountTransaction { // Create copies of state and resources for the execution. // Both will be rolled back if the execution is reverted or committed upon success. let mut execution_resources = resources.clone(); - let mut execution_state = CachedState::create_transactional(state); + let mut execution_state = TransactionalState::create_transactional(state); let execution_result = self.run_execute( &mut execution_state, @@ -628,9 +629,9 @@ impl AccountTransaction { } /// Runs validation and execution. - fn run_or_revert( + fn run_or_revert( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, remaining_gas: &mut u64, tx_context: Arc, validate: bool, @@ -644,10 +645,10 @@ impl AccountTransaction { } } -impl ExecutableTransaction for AccountTransaction { +impl ExecutableTransaction for AccountTransaction { fn execute_raw( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, block_context: &BlockContext, charge_fee: bool, validate: bool, diff --git a/crates/blockifier/src/transaction/account_transactions_test.rs b/crates/blockifier/src/transaction/account_transactions_test.rs index f5e9b5f0314..a670114c6ef 100644 --- a/crates/blockifier/src/transaction/account_transactions_test.rs +++ b/crates/blockifier/src/transaction/account_transactions_test.rs @@ -24,15 +24,14 @@ use crate::abi::abi_utils::{ get_storage_var_address, selector_from_name, }; -use crate::abi::sierra_types::next_storage_key; use crate::context::BlockContext; use crate::execution::contract_class::{ContractClass, ContractClassV1}; use crate::execution::entry_point::EntryPointExecutionContext; use crate::execution::execution_utils::{felt_to_stark_felt, stark_felt_to_felt}; use crate::execution::syscalls::SyscallSelector; -use crate::fee::fee_utils::get_fee_by_gas_vector; +use crate::fee::fee_utils::{get_fee_by_gas_vector, get_sequencer_balance_keys}; use crate::fee::gas_usage::estimate_minimal_gas_vector; -use crate::state::cached_state::{CachedState, StateChangesCount}; +use crate::state::cached_state::{StateChangesCount, TransactionalState}; use crate::state::state_api::{State, StateReader}; use crate::test_utils::contracts::FeatureContract; use crate::test_utils::declare::declare_tx; @@ -503,67 +502,6 @@ fn test_revert_invoke( ); } -#[rstest] -/// Tests that hitting an execution error in an account contract constructor outputs the correct -/// traceback (including correct class hash, contract address and constructor entry point selector). -fn test_account_ctor_frame_stack_trace( - block_context: BlockContext, - #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, -) { - let chain_info = &block_context.chain_info; - let faulty_account = FeatureContract::FaultyAccount(cairo_version); - let state = &mut test_state(chain_info, BALANCE, &[(faulty_account, 0)]); - let class_hash = faulty_account.get_class_hash(); - - // Create and execute deploy account transaction that passes validation and fails in the ctor. - let deploy_account_tx = create_account_tx_for_validate_test( - &mut NonceManager::default(), - FaultyAccountTxCreatorArgs { - tx_type: TransactionType::DeployAccount, - scenario: INVALID, - class_hash, - max_fee: Fee(BALANCE), - validate_constructor: true, - ..Default::default() - }, - ); - - // Fund the account so it can afford the deployment. - let deploy_address = match &deploy_account_tx { - AccountTransaction::DeployAccount(deploy_tx) => deploy_tx.contract_address, - _ => unreachable!("deploy_account_tx is a DeployAccount"), - }; - fund_account(chain_info, deploy_address, BALANCE * 2, &mut state.state); - - let expected_selector = selector_from_name("constructor").0; - let expected_address = deploy_address.0.key(); - let expected_error = format!( - "Contract constructor execution has failed: -0: Error in the called contract (contract address: {expected_address}, class hash: {class_hash}, \ - selector: {expected_selector}): -" - ) + match cairo_version { - CairoVersion::Cairo0 => { - "Error at pc=0:223: -Cairo traceback (most recent call last): -Unknown location (pc=0:195) -Unknown location (pc=0:179) - -An ASSERT_EQ instruction failed: 1 != 0. -" - } - CairoVersion::Cairo1 => { - "Execution failed. Failure reason: 0x496e76616c6964207363656e6172696f ('Invalid \ - scenario'). -" - } - }; - - // Compare expected and actual error. - let error = deploy_account_tx.execute(state, &block_context, true, true).unwrap_err(); - assert_eq!(error.to_string(), expected_error); -} - #[rstest] /// Tests that failing account deployment should not change state (no fee charge or nonce bump). fn test_fail_deploy_account( @@ -1083,6 +1021,7 @@ fn test_count_actual_storage_changes( #[values(CairoVersion::Cairo0, CairoVersion::Cairo1)] cairo_version: CairoVersion, ) { // FeeType according to version. + let chain_info = &block_context.chain_info; let fee_token_address = chain_info.fee_token_address(&fee_type); @@ -1117,7 +1056,7 @@ fn test_count_actual_storage_changes( // Run transactions; using transactional state to count only storage changes of the current // transaction. // First transaction: storage cell value changes from 0 to 1. - let mut state = CachedState::create_transactional(&mut state); + let mut state = TransactionalState::create_transactional(&mut state); let invoke_args = invoke_tx_args! { max_fee, resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), @@ -1162,11 +1101,11 @@ fn test_count_actual_storage_changes( }; assert_eq!(expected_modified_contracts, state_changes_1.get_modified_contracts()); - assert_eq!(expected_storage_updates_1, state_changes_1.storage_updates); + assert_eq!(expected_storage_updates_1, state_changes_1.0.storage); assert_eq!(state_changes_count_1, expected_state_changes_count_1); // Second transaction: storage cell starts and ends with value 1. - let mut state = CachedState::create_transactional(&mut state); + let mut state = TransactionalState::create_transactional(&mut state); let account_tx = account_invoke_tx(InvokeTxArgs { nonce: nonce_manager.next(account_address), ..invoke_args.clone() @@ -1198,11 +1137,11 @@ fn test_count_actual_storage_changes( }; assert_eq!(expected_modified_contracts_2, state_changes_2.get_modified_contracts()); - assert_eq!(expected_storage_updates_2, state_changes_2.storage_updates); + assert_eq!(expected_storage_updates_2, state_changes_2.0.storage); assert_eq!(state_changes_count_2, expected_state_changes_count_2); // Transfer transaction: transfer 1 ETH to recepient. - let mut state = CachedState::create_transactional(&mut state); + let mut state = TransactionalState::create_transactional(&mut state); let account_tx = account_invoke_tx(InvokeTxArgs { nonce: nonce_manager.next(account_address), calldata: transfer_calldata, @@ -1246,41 +1185,34 @@ fn test_count_actual_storage_changes( expected_modified_contracts_transfer, state_changes_transfer.get_modified_contracts() ); - assert_eq!(expected_storage_update_transfer, state_changes_transfer.storage_updates); + assert_eq!(expected_storage_update_transfer, state_changes_transfer.0.storage); assert_eq!(state_changes_count_3, expected_state_changes_count_3); } #[rstest] fn test_concurrency_execute_fee_transfer(#[values(FeeType::Eth, FeeType::Strk)] fee_type: FeeType) { - const STORAGE_WRITE_HIGH: u128 = 150; + // TODO(Meshi, 01/06/2024): make the test so it will include changes in + // sequencer_balance_key_high. const STORAGE_WRITE_LOW: u128 = 100; const STORAGE_READ_LOW: u128 = 50; let block_context = BlockContext::create_for_account_testing_with_concurrency_mode(true); - let empty_contract = FeatureContract::Empty(CairoVersion::Cairo1); let account = FeatureContract::AccountWithoutValidations(CairoVersion::Cairo1); + let account_tx = account_invoke_tx(invoke_tx_args! { + sender_address: account.get_instance_address(0), + calldata: create_trivial_calldata(account.get_instance_address(0)), + resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), + version: TransactionVersion::THREE + }); let chain_info = &block_context.chain_info; + let fee_token_address = block_context.chain_info.fee_token_address(&fee_type); let state = &mut test_state(chain_info, BALANCE, &[(account, 1)]); - let class_hash = empty_contract.get_class_hash(); - let class_info = calculate_class_info_for_testing(empty_contract.get_class()); - let sender_address = account.get_instance_address(0); - let account_tx = declare_tx( - declare_tx_args! { - sender_address, - version: TransactionVersion::THREE, - resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), - class_hash, - }, - class_info.clone(), - ); + let (sequencer_balance_key_low, sequencer_balance_key_high) = + get_sequencer_balance_keys(&block_context); - let fee_token_address = block_context.chain_info.fee_token_address(&fee_type); - let sequencer_address = block_context.block_info.sequencer_address; - let sequencer_balance_key_low = get_fee_token_var_address(sequencer_address); - let sequencer_balance_key_high = next_storage_key(&sequencer_balance_key_low).unwrap(); // Case 1: The transaction did not read form/ write to the sequenser balance before executing // fee transfer. - let mut transactional_state = CachedState::create_transactional(state); + let mut transactional_state = TransactionalState::create_transactional(state); account_tx.execute_raw(&mut transactional_state, &block_context, true, false).unwrap(); let transactional_cache = transactional_state.cache.borrow(); for storage in [ @@ -1295,40 +1227,52 @@ fn test_concurrency_execute_fee_transfer(#[values(FeeType::Eth, FeeType::Strk)] // Case 2: The transaction read from and write to the sequenser balance before executing fee // transfer. + let transfer_calldata = create_calldata( + fee_token_address, + TRANSFER_ENTRY_POINT_NAME, + &[ + *block_context.block_info.sequencer_address.0.key(), + stark_felt!(STORAGE_WRITE_LOW), + stark_felt!(0_u8), + ], + ); + // Set the sequencer balance to a constant value to check that the read set did not changed. - fund_account(chain_info, sequencer_address, STORAGE_READ_LOW, &mut state.state); - let mut transactional_state = CachedState::create_transactional(state); - - // Set the sequencer balance write set to a constant value. - // Note that it is enough to set the storage_write as execute_raw will update the - // storage_initial_values. - for (seq_key, value) in [ - (sequencer_balance_key_low, STORAGE_WRITE_LOW), - (sequencer_balance_key_high, STORAGE_WRITE_HIGH), - ] { - transactional_state.set_storage_at(fee_token_address, seq_key, stark_felt!(value)).unwrap(); - } + fund_account( + chain_info, + block_context.block_info.sequencer_address, + STORAGE_READ_LOW, + &mut state.state, + ); + let mut transactional_state = TransactionalState::create_transactional(state); + + // Invokes transfer to the sequencer. + let account_tx = account_invoke_tx(invoke_tx_args! { + sender_address: account.get_instance_address(0), + calldata: transfer_calldata, + resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), + version: TransactionVersion::THREE + }); + + account_tx.execute_raw(&mut transactional_state, &block_context, true, true).unwrap(); - account_tx.execute_raw(&mut transactional_state, &block_context, true, false).unwrap(); // Check that the sequencer balance was not changed. - let storage_write = transactional_state.cache.borrow().writes.storage.clone(); - let storage_initial_values = transactional_state.cache.borrow().initial_reads.storage.clone(); + let storage_writes = transactional_state.cache.borrow().writes.storage.clone(); + let storage_initial_reads = transactional_state.cache.borrow().initial_reads.storage.clone(); for (seq_write_val, expexted_write_val) in [ ( - storage_write.get(&(fee_token_address, sequencer_balance_key_low)), - stark_felt!(STORAGE_WRITE_LOW), + storage_writes.get(&(fee_token_address, sequencer_balance_key_low)), + // Balance after `execute` and without the fee transfer. + stark_felt!(STORAGE_WRITE_LOW + STORAGE_READ_LOW), ), ( - storage_initial_values.get(&(fee_token_address, sequencer_balance_key_low)), + storage_initial_reads.get(&(fee_token_address, sequencer_balance_key_low)), stark_felt!(STORAGE_READ_LOW), ), + (storage_writes.get(&(fee_token_address, sequencer_balance_key_high)), StarkFelt::ZERO), ( - storage_write.get(&(fee_token_address, sequencer_balance_key_high)), - stark_felt!(STORAGE_WRITE_HIGH), - ), - ( - storage_initial_values.get(&(fee_token_address, sequencer_balance_key_high)), + storage_initial_reads.get(&(fee_token_address, sequencer_balance_key_high)), StarkFelt::ZERO, ), ] { diff --git a/crates/blockifier/src/transaction/constants.rs b/crates/blockifier/src/transaction/constants.rs index c7e185b682d..16b5d8cb6c7 100644 --- a/crates/blockifier/src/transaction/constants.rs +++ b/crates/blockifier/src/transaction/constants.rs @@ -3,6 +3,7 @@ pub const TRANSFER_ENTRY_POINT_NAME: &str = "transfer"; pub const VALIDATE_ENTRY_POINT_NAME: &str = "__validate__"; pub const VALIDATE_DECLARE_ENTRY_POINT_NAME: &str = "__validate_declare__"; pub const VALIDATE_DEPLOY_ENTRY_POINT_NAME: &str = "__validate_deploy__"; +pub const DEPLOY_CONTRACT_FUNCTION_ENTRY_POINT_NAME: &str = "deploy_contract"; pub const TRANSFER_EVENT_NAME: &str = "Transfer"; diff --git a/crates/blockifier/src/transaction/errors.rs b/crates/blockifier/src/transaction/errors.rs index bbe81e14b40..1318eaa7d03 100644 --- a/crates/blockifier/src/transaction/errors.rs +++ b/crates/blockifier/src/transaction/errors.rs @@ -5,27 +5,25 @@ use starknet_api::StarknetApiError; use thiserror::Error; use crate::execution::call_info::Retdata; -use crate::execution::errors::{ - gen_transaction_execution_error_trace, - ConstructorEntryPointExecutionError, - EntryPointExecutionError, -}; +use crate::execution::errors::{ConstructorEntryPointExecutionError, EntryPointExecutionError}; +use crate::execution::stack_trace::gen_transaction_execution_error_trace; use crate::fee::fee_checks::FeeCheckError; use crate::state::errors::StateError; +// TODO(Yoni, 1/9/2024): implement Display for Fee. #[derive(Debug, Error)] pub enum TransactionFeeError { #[error("Cairo resource names must be contained in fee cost dict.")] CairoResourcesNotContainedInFeeCosts, #[error(transparent)] ExecuteFeeTransferError(#[from] EntryPointExecutionError), - #[error("Actual fee ({actual_fee:?}) exceeded max fee ({max_fee:?}).")] + #[error("Actual fee ({}) exceeded max fee ({}).", actual_fee.0, max_fee.0)] FeeTransferError { max_fee: Fee, actual_fee: Fee }, - #[error("Actual fee ({actual_fee:?}) exceeded paid fee on L1 ({paid_fee:?}).")] + #[error("Actual fee ({}) exceeded paid fee on L1 ({}).", actual_fee.0, paid_fee.0)] InsufficientL1Fee { paid_fee: Fee, actual_fee: Fee }, #[error( - "L1 gas bounds (max amount: {max_amount:?}, max price: {max_price:?}) exceed balance \ - (Uint256({balance_low:?}, {balance_high:?}))." + "L1 gas bounds (max amount: {max_amount}, max price: {max_price}) exceed balance \ + (Uint256({balance_low}, {balance_high}))." )] L1GasBoundsExceedBalance { max_amount: u64, @@ -33,18 +31,18 @@ pub enum TransactionFeeError { balance_low: StarkFelt, balance_high: StarkFelt, }, - #[error("Max fee ({max_fee:?}) exceeds balance (Uint256({balance_low:?}, {balance_high:?})).")] + #[error("Max fee ({}) exceeds balance (Uint256({balance_low}, {balance_high})).", max_fee.0)] MaxFeeExceedsBalance { max_fee: Fee, balance_low: StarkFelt, balance_high: StarkFelt }, - #[error("Max fee ({max_fee:?}) is too low. Minimum fee: {min_fee:?}.")] + #[error("Max fee ({}) is too low. Minimum fee: {}.", max_fee.0, min_fee.0)] MaxFeeTooLow { min_fee: Fee, max_fee: Fee }, #[error( - "Max L1 gas price ({max_l1_gas_price:?}) is lower than the actual gas price: \ - {actual_l1_gas_price:?}." + "Max L1 gas price ({max_l1_gas_price}) is lower than the actual gas price: \ + {actual_l1_gas_price}." )] MaxL1GasPriceTooLow { max_l1_gas_price: u128, actual_l1_gas_price: u128 }, #[error( - "Max L1 gas amount ({max_l1_gas_amount:?}) is lower than the minimal gas amount: \ - {minimal_l1_gas_amount:?}." + "Max L1 gas amount ({max_l1_gas_amount}) is lower than the minimal gas amount: \ + {minimal_l1_gas_amount}." )] MaxL1GasAmountTooLow { max_l1_gas_amount: u64, minimal_l1_gas_amount: u64 }, #[error("Missing L1 gas bounds in resource bounds.")] @@ -62,12 +60,15 @@ pub enum TransactionExecutionError { ContractClassVersionMismatch { declare_version: TransactionVersion, cairo_version: u64 }, #[error( "Contract constructor execution has failed:\n{}", - gen_transaction_execution_error_trace(self) + String::from(gen_transaction_execution_error_trace(self)) )] ContractConstructorExecutionFailed(#[from] ConstructorEntryPointExecutionError), #[error("Class with hash {class_hash:?} is already declared.")] DeclareTransactionError { class_hash: ClassHash }, - #[error("Transaction execution has failed:\n{}", gen_transaction_execution_error_trace(self))] + #[error( + "Transaction execution has failed:\n{}", + String::from(gen_transaction_execution_error_trace(self)) + )] ExecutionError { error: EntryPointExecutionError, class_hash: ClassHash, @@ -95,7 +96,10 @@ pub enum TransactionExecutionError { TryFromIntError(#[from] std::num::TryFromIntError), #[error("Transaction size exceeds the maximum block capacity.")] TransactionTooLarge, - #[error("Transaction validation has failed:\n{}", gen_transaction_execution_error_trace(self))] + #[error( + "Transaction validation has failed:\n{}", + String::from(gen_transaction_execution_error_trace(self)) + )] ValidateTransactionError { error: EntryPointExecutionError, class_hash: ClassHash, diff --git a/crates/blockifier/src/transaction/objects.rs b/crates/blockifier/src/transaction/objects.rs index 837084cebb9..5e299cb7ac2 100644 --- a/crates/blockifier/src/transaction/objects.rs +++ b/crates/blockifier/src/transaction/objects.rs @@ -480,9 +480,9 @@ impl TransactionResources { .expect("This conversion should not fail as the value is a converted usize."), ), ])); - let revrted_steps_to_add = if with_reverted_steps { self.n_reverted_steps } else { 0 }; + let reverted_steps_to_add = if with_reverted_steps { self.n_reverted_steps } else { 0 }; *resources.0.get_mut(abi_constants::N_STEPS_RESOURCE).unwrap_or(&mut 0) += - revrted_steps_to_add; + reverted_steps_to_add; resources } diff --git a/crates/blockifier/src/transaction/test_utils.rs b/crates/blockifier/src/transaction/test_utils.rs index e0549c40a12..3b51588a888 100644 --- a/crates/blockifier/src/transaction/test_utils.rs +++ b/crates/blockifier/src/transaction/test_utils.rs @@ -223,6 +223,7 @@ pub fn create_account_tx_for_validate_test( version: tx_version, nonce: nonce_manager.next(sender_address), class_hash, + compiled_class_hash: declared_contract.get_compiled_class_hash(), }, class_info, ) diff --git a/crates/blockifier/src/transaction/transaction_execution.rs b/crates/blockifier/src/transaction/transaction_execution.rs index cc08fb13441..47312d3f582 100644 --- a/crates/blockifier/src/transaction/transaction_execution.rs +++ b/crates/blockifier/src/transaction/transaction_execution.rs @@ -9,7 +9,7 @@ use crate::execution::contract_class::ClassInfo; use crate::execution::entry_point::EntryPointExecutionContext; use crate::fee::actual_cost::TransactionReceipt; use crate::state::cached_state::TransactionalState; -use crate::state::state_api::StateReader; +use crate::state::state_api::UpdatableState; use crate::transaction::account_transaction::AccountTransaction; use crate::transaction::errors::TransactionFeeError; use crate::transaction::objects::{ @@ -106,10 +106,10 @@ impl TransactionInfoCreator for Transaction { } } -impl ExecutableTransaction for L1HandlerTransaction { +impl ExecutableTransaction for L1HandlerTransaction { fn execute_raw( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, block_context: &BlockContext, _charge_fee: bool, _validate: bool, @@ -151,10 +151,10 @@ impl ExecutableTransaction for L1HandlerTransaction { } } -impl ExecutableTransaction for Transaction { +impl ExecutableTransaction for Transaction { fn execute_raw( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, block_context: &BlockContext, charge_fee: bool, validate: bool, diff --git a/crates/blockifier/src/transaction/transactions.rs b/crates/blockifier/src/transaction/transactions.rs index a557145f5ff..6babd1c78b8 100644 --- a/crates/blockifier/src/transaction/transactions.rs +++ b/crates/blockifier/src/transaction/transactions.rs @@ -28,9 +28,9 @@ use crate::execution::entry_point::{ EntryPointExecutionContext, }; use crate::execution::execution_utils::execute_deployment; -use crate::state::cached_state::{CachedState, TransactionalState}; +use crate::state::cached_state::TransactionalState; use crate::state::errors::StateError; -use crate::state::state_api::{State, StateReader}; +use crate::state::state_api::{State, UpdatableState}; use crate::transaction::constants; use crate::transaction::errors::TransactionExecutionError; use crate::transaction::objects::{ @@ -57,18 +57,18 @@ macro_rules! implement_inner_tx_getter_calls { }; } -pub trait ExecutableTransaction: Sized { +pub trait ExecutableTransaction: Sized { /// Executes the transaction in a transactional manner /// (if it fails, given state does not modify). fn execute( &self, - state: &mut CachedState, + state: &mut U, block_context: &BlockContext, charge_fee: bool, validate: bool, ) -> TransactionExecutionResult { log::debug!("Executing Transaction..."); - let mut transactional_state = CachedState::create_transactional(state); + let mut transactional_state = TransactionalState::create_transactional(state); let execution_result = self.execute_raw(&mut transactional_state, block_context, charge_fee, validate); @@ -92,7 +92,7 @@ pub trait ExecutableTransaction: Sized { /// for automatic handling of such cases. fn execute_raw( &self, - state: &mut TransactionalState<'_, S>, + state: &mut TransactionalState<'_, U>, block_context: &BlockContext, charge_fee: bool, validate: bool, diff --git a/crates/blockifier/src/transaction/transactions_test.rs b/crates/blockifier/src/transaction/transactions_test.rs index 851eb8b4924..3df6237ad5a 100644 --- a/crates/blockifier/src/transaction/transactions_test.rs +++ b/crates/blockifier/src/transaction/transactions_test.rs @@ -53,7 +53,7 @@ use crate::fee::gas_usage::{ get_da_gas_cost, get_onchain_data_segment_length, }; -use crate::state::cached_state::{CachedState, StateChangesCount}; +use crate::state::cached_state::{CachedState, StateChangesCount, TransactionalState}; use crate::state::errors::StateError; use crate::state::state_api::{State, StateReader}; use crate::test_utils::contracts::FeatureContract; @@ -265,15 +265,10 @@ fn expected_fee_transfer_call_info( ..Default::default() }, resources: Prices::FeeTransfer(account_address, *fee_type).into(), - // We read sender balance, write (which starts with read) sender balance, then the same for - // recipient. We read Uint256(BALANCE, 0) twice, then Uint256(0, 0) twice. + // We read sender and recipient balance - Uint256(BALANCE, 0) then Uint256(0, 0). storage_read_values: vec![ stark_felt!(BALANCE), stark_felt!(0_u8), - stark_felt!(BALANCE), - stark_felt!(0_u8), - stark_felt!(0_u8), - stark_felt!(0_u8), stark_felt!(0_u8), stark_felt!(0_u8), ], @@ -885,6 +880,7 @@ fn test_max_fee_exceeds_balance( let invalid_tx = declare_tx( declare_tx_args! { class_hash: contract_to_declare.get_class_hash(), + compiled_class_hash: contract_to_declare.get_compiled_class_hash(), sender_address: account_contract_address, max_fee: invalid_max_fee, }, @@ -1035,7 +1031,7 @@ fn test_invalid_nonce( calldata: create_trivial_calldata(test_contract.get_instance_address(0)), max_fee: Fee(MAX_FEE) }; - let mut transactional_state = CachedState::create_transactional(state); + let mut transactional_state = TransactionalState::create_transactional(state); // Strict, negative flow: account nonce = 0, incoming tx nonce = 1. let invalid_nonce = nonce!(1_u8); @@ -1148,6 +1144,7 @@ fn test_declare_tx( let chain_info = &block_context.chain_info; let state = &mut test_state(chain_info, BALANCE, &[(account, 1)]); let class_hash = empty_contract.get_class_hash(); + let compiled_class_hash = empty_contract.get_compiled_class_hash(); let class_info = calculate_class_info_for_testing(empty_contract.get_class()); let sender_address = account.get_instance_address(0); let starknet_resources = StarknetResources::new( @@ -1166,6 +1163,7 @@ fn test_declare_tx( version: tx_version, resource_bounds: l1_resource_bounds(MAX_L1_GAS_AMOUNT, MAX_L1_GAS_PRICE), class_hash, + compiled_class_hash, }, class_info.clone(), ); diff --git a/crates/blockifier/tests/feature_contracts_compatibility_test.rs b/crates/blockifier/tests/feature_contracts_compatibility_test.rs index 34d77a229bf..c9caf8d0cf3 100644 --- a/crates/blockifier/tests/feature_contracts_compatibility_test.rs +++ b/crates/blockifier/tests/feature_contracts_compatibility_test.rs @@ -1,7 +1,12 @@ use std::fs; use std::process::Command; -const FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo0"; +use blockifier::test_utils::contracts::FeatureContract; +use blockifier::test_utils::CairoVersion; +use pretty_assertions::assert_eq; + +const CAIRO0_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo0"; +const CAIRO1_FEATURE_CONTRACTS_DIR: &str = "feature_contracts/cairo1"; const COMPILED_CONTRACTS_SUBDIR: &str = "compiled"; const FIX_COMMAND: &str = "FIX_FEATURE_TEST=1 cargo test -- --ignored"; @@ -35,35 +40,9 @@ const FIX_COMMAND: &str = "FIX_FEATURE_TEST=1 cargo test -- --ignored"; // `COMPILED_CONTRACTS_SUBDIR`. // 2. for each `X.cairo` file in `TEST_CONTRACTS` there exists an `X_compiled.json` file in // `COMPILED_CONTRACTS_SUBDIR` which equals `starknet-compile-deprecated X.cairo --no_debug_info`. -fn verify_feature_contracts_compatibility(fix: bool) { - for file in fs::read_dir(FEATURE_CONTRACTS_DIR).unwrap() { - let path = file.unwrap().path(); - - // Test `TEST_CONTRACTS` file and directory structure. - if !path.is_file() { - if let Some(dir_name) = path.file_name() { - assert_eq!( - dir_name, - COMPILED_CONTRACTS_SUBDIR, - "Found directory '{}' in `{FEATURE_CONTRACTS_DIR}`, which should contain only \ - the `{COMPILED_CONTRACTS_SUBDIR}` directory.", - dir_name.to_string_lossy() - ); - continue; - } - } - let path_str = path.to_string_lossy(); - assert_eq!( - path.extension().unwrap(), - "cairo", - "Found a non-Cairo file '{path_str}' in `{FEATURE_CONTRACTS_DIR}`" - ); - +fn verify_feature_contracts_compatibility(fix: bool, cairo_version: CairoVersion) { + for (path_str, file_name, existing_compiled_path) in verify_and_get_files(cairo_version) { // Compare output of cairo-file on file with existing compiled file. - let file_name = path.file_stem().unwrap().to_string_lossy(); - let existing_compiled_path = format!( - "{FEATURE_CONTRACTS_DIR}/{COMPILED_CONTRACTS_SUBDIR}/{file_name}_compiled.json" - ); let mut command = Command::new("starknet-compile-deprecated"); command.args([&path_str, "--no_debug_info"]); if file_name.starts_with("account") { @@ -93,9 +72,71 @@ fn verify_feature_contracts_compatibility(fix: bool) { } } +/// Verifies that the feature contracts directory contains the expected contents, and returns a list +/// of pairs (source_path, base_filename, compiled_path) for each contract. +fn verify_and_get_files(cairo_version: CairoVersion) -> Vec<(String, String, String)> { + let mut paths = vec![]; + let directory = match cairo_version { + CairoVersion::Cairo0 => CAIRO0_FEATURE_CONTRACTS_DIR, + CairoVersion::Cairo1 => CAIRO1_FEATURE_CONTRACTS_DIR, + }; + let compiled_extension = match cairo_version { + CairoVersion::Cairo0 => "_compiled.json", + CairoVersion::Cairo1 => ".casm.json", + }; + for file in fs::read_dir(directory).unwrap() { + let path = file.unwrap().path(); + + // Verify `TEST_CONTRACTS` file and directory structure. + if !path.is_file() { + if let Some(dir_name) = path.file_name() { + assert_eq!( + dir_name, + COMPILED_CONTRACTS_SUBDIR, + "Found directory '{}' in `{directory}`, which should contain only the \ + `{COMPILED_CONTRACTS_SUBDIR}` directory.", + dir_name.to_string_lossy() + ); + continue; + } + } + let path_str = path.to_string_lossy(); + assert_eq!( + path.extension().unwrap(), + "cairo", + "Found a non-Cairo file '{path_str}' in `{directory}`" + ); + + let file_name = path.file_stem().unwrap().to_string_lossy(); + let existing_compiled_path = + format!("{directory}/{COMPILED_CONTRACTS_SUBDIR}/{file_name}{compiled_extension}"); + + paths.push((path_str.to_string(), file_name.to_string(), existing_compiled_path)); + } + + paths +} + +#[test] +fn verify_feature_contracts_match_enum() { + let mut compiled_paths_from_enum: Vec = FeatureContract::all_contracts() + // ERC20 is a special case - not in the feature_contracts directory. + .filter(|contract| !matches!(contract, FeatureContract::ERC20)) + .map(|contract| contract.get_compiled_path()) + .collect(); + let mut compiled_paths_on_filesystem: Vec = verify_and_get_files(CairoVersion::Cairo0) + .into_iter() + .chain(verify_and_get_files(CairoVersion::Cairo1)) + .map(|(_, _, compiled_path)| compiled_path) + .collect(); + compiled_paths_from_enum.sort(); + compiled_paths_on_filesystem.sort(); + assert_eq!(compiled_paths_from_enum, compiled_paths_on_filesystem); +} + #[test] #[ignore] fn verify_feature_contracts() { let fix_features = std::env::var("FIX_FEATURE_TEST").is_ok(); - verify_feature_contracts_compatibility(fix_features) + verify_feature_contracts_compatibility(fix_features, CairoVersion::Cairo0) } diff --git a/crates/gateway/Cargo.toml b/crates/gateway/Cargo.toml index 3afd73b4720..677032d05b3 100644 --- a/crates/gateway/Cargo.toml +++ b/crates/gateway/Cargo.toml @@ -8,24 +8,30 @@ license.workspace = true [lints] workspace = true +[features] +testing = [] + [dependencies] -# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. -starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", rev = "1b46b42" } -# TODO(YaelD, 1/5/2024): Use a fixed version once the StarkNet API is stable. -blockifier = { git = "https://github.com/starkware-libs/blockifier.git", rev = "ba72c863", features = ["testing"] } axum.workspace = true -clap.workspace = true -papyrus_config.workspace = true +# TODO(YaelD, 1/5/2024): Use a fixed version once the StarkNet API is stable. +blockifier = { git = "https://github.com/starkware-libs/blockifier.git", rev = "6babc28a", features = [ + "testing", +] } +cairo-lang-starknet-classes.workspace = true +mempool_infra = { path = "../mempool_infra", version = "0.4.0-dev.2" } +papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } reqwest.workspace = true serde.workspace = true serde_json.workspace = true +# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. +starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "main-mempool" } +starknet_mempool_types = { path = "../mempool_types", version = "0.4.0-dev.2" } thiserror.workspace = true -tower.workspace = true -url.workspace = true +tokio.workspace = true validator.workspace = true [dev-dependencies] assert_matches.workspace = true pretty_assertions.workspace = true rstest.workspace = true -tokio.workspace = true +starknet_mempool = { path = "../mempool", version = "0.4.0-dev.2" } diff --git a/crates/gateway/src/config.rs b/crates/gateway/src/config.rs index 329cf85be9b..431042dd679 100644 --- a/crates/gateway/src/config.rs +++ b/crates/gateway/src/config.rs @@ -1,19 +1,47 @@ use std::collections::BTreeMap; use std::net::IpAddr; -use papyrus_config::dumping::{ser_param, SerializeConfig}; +use blockifier::context::{BlockContext, ChainInfo, FeeTokenAddresses}; +use papyrus_config::dumping::{append_sub_config_name, ser_param, SerializeConfig}; use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; use serde::{Deserialize, Serialize}; +use starknet_api::core::{ChainId, ContractAddress, Nonce}; use validator::Validate; -/// The gateway configuration. -#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] pub struct GatewayConfig { + pub network_config: GatewayNetworkConfig, + pub stateless_tx_validator_config: StatelessTransactionValidatorConfig, + pub stateful_tx_validator_config: StatefulTransactionValidatorConfig, +} + +impl SerializeConfig for GatewayConfig { + fn dump(&self) -> BTreeMap { + vec![ + append_sub_config_name(self.network_config.dump(), "network_config"), + append_sub_config_name( + self.stateless_tx_validator_config.dump(), + "stateless_tx_validator_config", + ), + append_sub_config_name( + self.stateful_tx_validator_config.dump(), + "stateful_tx_validator_config", + ), + ] + .into_iter() + .flatten() + .collect() + } +} + +/// The gateway network connection related configuration. +#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] +pub struct GatewayNetworkConfig { pub ip: IpAddr, pub port: u16, } -impl SerializeConfig for GatewayConfig { +impl SerializeConfig for GatewayNetworkConfig { fn dump(&self) -> BTreeMap { BTreeMap::from_iter([ ser_param( @@ -27,8 +55,189 @@ impl SerializeConfig for GatewayConfig { } } -impl Default for GatewayConfig { +impl Default for GatewayNetworkConfig { fn default() -> Self { Self { ip: "0.0.0.0".parse().unwrap(), port: 8080 } } } + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] +pub struct StatelessTransactionValidatorConfig { + // If true, validates that the resource bounds are not zero. + pub validate_non_zero_l1_gas_fee: bool, + pub validate_non_zero_l2_gas_fee: bool, + + pub max_calldata_length: usize, + pub max_signature_length: usize, +} + +impl SerializeConfig for StatelessTransactionValidatorConfig { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ + ser_param( + "validate_non_zero_l1_gas_fee", + &self.validate_non_zero_l1_gas_fee, + "If true, validates that a transaction has non-zero L1 resource bounds.", + ParamPrivacyInput::Public, + ), + ser_param( + "validate_non_zero_l2_gas_fee", + &self.validate_non_zero_l2_gas_fee, + "If true, validates that a transaction has non-zero L2 resource bounds.", + ParamPrivacyInput::Public, + ), + ser_param( + "max_signature_length", + &self.max_signature_length, + "Validates that a transaction has calldata length less than or equal to this \ + value.", + ParamPrivacyInput::Public, + ), + ser_param( + "max_calldata_length", + &self.max_calldata_length, + "Validates that a transaction has signature length less than or equal to this \ + value.", + ParamPrivacyInput::Public, + ), + ]) + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] +pub struct RpcStateReaderConfig { + pub url: String, + pub json_rpc_version: String, +} + +#[cfg(any(feature = "testing", test))] +impl RpcStateReaderConfig { + pub fn create_for_testing() -> Self { + Self { url: "http://localhost:8080".to_string(), json_rpc_version: "2.0".to_string() } + } +} + +impl SerializeConfig for RpcStateReaderConfig { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ + ser_param("url", &self.url, "The url of the rpc server.", ParamPrivacyInput::Public), + ser_param( + "json_rpc_version", + &self.json_rpc_version, + "The json rpc version.", + ParamPrivacyInput::Public, + ), + ]) + } +} + +// TODO(Arni): Remove this struct once Chain info supports Papyrus serialization. +#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq)] +pub struct ChainInfoConfig { + pub chain_id: ChainId, + pub strk_fee_token_address: ContractAddress, + pub eth_fee_token_address: ContractAddress, +} + +impl From for ChainInfo { + fn from(chain_info: ChainInfoConfig) -> Self { + Self { + chain_id: chain_info.chain_id, + fee_token_addresses: FeeTokenAddresses { + strk_fee_token_address: chain_info.strk_fee_token_address, + eth_fee_token_address: chain_info.eth_fee_token_address, + }, + } + } +} + +impl From for ChainInfoConfig { + fn from(chain_info: ChainInfo) -> Self { + let FeeTokenAddresses { strk_fee_token_address, eth_fee_token_address } = + chain_info.fee_token_addresses; + Self { chain_id: chain_info.chain_id, strk_fee_token_address, eth_fee_token_address } + } +} + +impl Default for ChainInfoConfig { + fn default() -> Self { + ChainInfo::default().into() + } +} + +impl ChainInfoConfig { + pub fn create_for_testing() -> Self { + BlockContext::create_for_testing().chain_info().clone().into() + } +} + +impl SerializeConfig for ChainInfoConfig { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ + ser_param( + "chain_id", + &self.chain_id, + "The chain ID of the StarkNet chain.", + ParamPrivacyInput::Public, + ), + ser_param( + "strk_fee_token_address", + &self.strk_fee_token_address, + "Address of the STRK fee token.", + ParamPrivacyInput::Public, + ), + ser_param( + "eth_fee_token_address", + &self.eth_fee_token_address, + "Address of the ETH fee token.", + ParamPrivacyInput::Public, + ), + ]) + } +} + +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] +pub struct StatefulTransactionValidatorConfig { + pub max_nonce_for_validation_skip: Nonce, + pub validate_max_n_steps: u32, + pub max_recursion_depth: usize, + pub chain_info: ChainInfoConfig, +} + +impl SerializeConfig for StatefulTransactionValidatorConfig { + fn dump(&self) -> BTreeMap { + let members = BTreeMap::from_iter([ + ser_param( + "max_nonce_for_validation_skip", + &self.max_nonce_for_validation_skip, + "The maximum nonce for which the validation is skipped.", + ParamPrivacyInput::Public, + ), + ser_param( + "validate_max_n_steps", + &self.validate_max_n_steps, + "The maximum number of steps the validation function is allowed to take.", + ParamPrivacyInput::Public, + ), + ser_param( + "max_recursion_depth", + &self.max_recursion_depth, + "The maximum recursion depth allowed in a transaction.", + ParamPrivacyInput::Public, + ), + ]); + let sub_configs = append_sub_config_name(self.chain_info.dump(), "chain_info"); + vec![members, sub_configs].into_iter().flatten().collect() + } +} + +impl StatefulTransactionValidatorConfig { + pub fn create_for_testing() -> Self { + StatefulTransactionValidatorConfig { + max_nonce_for_validation_skip: Default::default(), + validate_max_n_steps: 1000000, + max_recursion_depth: 50, + chain_info: ChainInfoConfig::create_for_testing(), + } + } +} diff --git a/crates/gateway/src/config_test.rs b/crates/gateway/src/config_test.rs deleted file mode 100644 index 90c999d0b7b..00000000000 --- a/crates/gateway/src/config_test.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::fs::File; -use std::path::Path; - -use clap::Command; -use papyrus_config::loading::load_and_process_config; -use validator::Validate; - -use crate::config::GatewayConfig; - -const TEST_FILES_FOLDER: &str = "./src/json_files_for_testing"; -const CONFIG_FILE: &str = "gateway_config.json"; - -fn get_config_file(file_name: &str) -> Result { - let config_file = File::open(Path::new(TEST_FILES_FOLDER).join(file_name)).unwrap(); - load_and_process_config::(config_file, Command::new(""), vec![]) -} - -#[test] -fn test_valid_config() { - // Read the valid config file and validate its content. - let expected_config = GatewayConfig { ip: "0.0.0.0".parse().unwrap(), port: 8080 }; - - let loaded_config = get_config_file(CONFIG_FILE).unwrap(); - - assert!(loaded_config.validate().is_ok()); - assert_eq!(loaded_config, expected_config); -} diff --git a/crates/gateway/src/errors.rs b/crates/gateway/src/errors.rs index 9424f320cea..544e278bfd7 100644 --- a/crates/gateway/src/errors.rs +++ b/crates/gateway/src/errors.rs @@ -1,16 +1,22 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; use blockifier::blockifier::stateful_validator::StatefulValidatorError; +use blockifier::state::errors::StateError; use blockifier::transaction::errors::TransactionExecutionError; use starknet_api::block::BlockNumber; use starknet_api::transaction::{Resource, ResourceBounds}; use starknet_api::StarknetApiError; use thiserror::Error; +use tokio::task::JoinError; #[derive(Debug, Error)] pub enum GatewayError { - #[error("Internal server error")] - InternalServerError, + #[error("Internal server error: {0}")] + InternalServerError(#[from] JoinError), + #[error("Error sending message: {0}")] + MessageSendError(String), + #[error(transparent)] + StatefulTransactionValidatorError(#[from] StatefulTransactionValidatorError), #[error(transparent)] StatelessTransactionValidatorError(#[from] StatelessTransactionValidatorError), } @@ -29,8 +35,6 @@ impl IntoResponse for GatewayError { pub enum StatelessTransactionValidatorError { #[error("Expected a positive amount of {resource:?}. Got {resource_bounds:?}.")] ZeroResourceBounds { resource: Resource, resource_bounds: ResourceBounds }, - #[error("The resource bounds mapping is missing a resource {resource:?}.")] - MissingResource { resource: Resource }, #[error( "Calldata length exceeded maximum: length {calldata_length} (allowed length: {max_calldata_length})." @@ -52,6 +56,8 @@ pub enum StatefulTransactionValidatorError { #[error(transparent)] StarknetApiError(#[from] StarknetApiError), #[error(transparent)] + StateError(#[from] StateError), + #[error(transparent)] StatefulValidatorError(#[from] StatefulValidatorError), #[error(transparent)] TransactionExecutionError(#[from] TransactionExecutionError), diff --git a/crates/gateway/src/gateway.rs b/crates/gateway/src/gateway.rs index 9b3f8ea6120..91075065596 100644 --- a/crates/gateway/src/gateway.rs +++ b/crates/gateway/src/gateway.rs @@ -1,11 +1,27 @@ +use std::clone::Clone; use std::net::SocketAddr; +use std::sync::Arc; +use axum::extract::State; use axum::routing::{get, post}; use axum::{Json, Router}; +use mempool_infra::network_component::CommunicationInterface; use starknet_api::external_transaction::ExternalTransaction; +use starknet_api::transaction::TransactionHash; +use starknet_mempool_types::mempool_types::{ + Account, + GatewayNetworkComponent, + GatewayToMempoolMessage, + MempoolInput, +}; -use crate::config::GatewayConfig; +use crate::config::{GatewayConfig, GatewayNetworkConfig}; use crate::errors::GatewayError; +use crate::starknet_api_test_utils::get_sender_address; +use crate::state_reader::StateReaderFactory; +use crate::stateful_transaction_validator::StatefulTransactionValidator; +use crate::stateless_transaction_validator::StatelessTransactionValidator; +use crate::utils::external_tx_to_thin_tx; #[cfg(test)] #[path = "gateway_test.rs"] @@ -14,35 +30,106 @@ pub mod gateway_test; pub type GatewayResult = Result; pub struct Gateway { - pub config: GatewayConfig, + config: GatewayConfig, + app_state: AppState, +} + +#[derive(Clone)] +pub struct AppState { + pub stateless_tx_validator: StatelessTransactionValidator, + pub stateful_tx_validator: Arc, + /// This field uses Arc to enable shared ownership, which is necessary because + /// `GatewayNetworkClient` supports only one receiver at a time. + pub network_component: Arc, + pub state_reader_factory: Arc, } impl Gateway { - pub async fn build_server(self) { + pub fn new( + config: GatewayConfig, + network_component: GatewayNetworkComponent, + state_reader_factory: Arc, + ) -> Self { + let app_state = AppState { + stateless_tx_validator: StatelessTransactionValidator { + config: config.stateless_tx_validator_config.clone(), + }, + stateful_tx_validator: Arc::new(StatefulTransactionValidator { + config: config.stateful_tx_validator_config.clone(), + }), + network_component: Arc::new(network_component), + state_reader_factory, + }; + Gateway { config, app_state } + } + + pub async fn run_server(self) { // Parses the bind address from GatewayConfig, returning an error for invalid addresses. - let addr = SocketAddr::new(self.config.ip, self.config.port); - let app = app(); + let GatewayNetworkConfig { ip, port } = self.config.network_config; + let addr = SocketAddr::new(ip, port); + let app = self.app(); // Create a server that runs forever. axum::Server::bind(&addr).serve(app.into_make_service()).await.unwrap(); } -} -/// Sets up the router with the specified routes for the server. -pub fn app() -> Router { - Router::new().route("/is_alive", get(is_alive)).route("/add_transaction", post(add_transaction)) - // TODO: when we need to configure the router, like adding banned ips, add it here via - // `with_state`. + pub fn app(self) -> Router { + Router::new() + .route("/is_alive", get(is_alive)) + .route("/add_tx", post(add_tx)) + .with_state(self.app_state) + // TODO: when we need to configure the router, like adding banned ips, add it here via + // `with_state`. + } } +// Gateway handlers. + async fn is_alive() -> GatewayResult { unimplemented!("Future handling should be implemented here."); } -async fn add_transaction(Json(transaction): Json) -> GatewayResult { - Ok(match transaction { - ExternalTransaction::Declare(_) => "DECLARE".into(), - ExternalTransaction::DeployAccount(_) => "DEPLOY_ACCOUNT".into(), - ExternalTransaction::Invoke(_) => "INVOKE".into(), +async fn add_tx( + State(app_state): State, + Json(tx): Json, +) -> GatewayResult> { + let mempool_input = tokio::task::spawn_blocking(move || { + process_tx( + app_state.stateless_tx_validator, + app_state.stateful_tx_validator.as_ref(), + app_state.state_reader_factory.as_ref(), + tx, + ) + }) + .await??; + + let tx_hash = mempool_input.tx.tx_hash; + let message = GatewayToMempoolMessage::AddTransaction(mempool_input); + app_state + .network_component + .send(message) + .await + .map_err(|e| GatewayError::MessageSendError(e.to_string()))?; + // TODO: Also return `ContractAddress` for deploy and `ClassHash` for Declare. + Ok(Json(tx_hash)) +} + +fn process_tx( + stateless_tx_validator: StatelessTransactionValidator, + stateful_tx_validator: &StatefulTransactionValidator, + state_reader_factory: &dyn StateReaderFactory, + tx: ExternalTransaction, +) -> GatewayResult { + // TODO(Arni, 1/5/2024): Preform congestion control. + + // Perform stateless validations. + stateless_tx_validator.validate(&tx)?; + + // TODO(Yael, 19/5/2024): pass the relevant class_info and deploy_account_hash. + let tx_hash = stateful_tx_validator.run_validate(state_reader_factory, &tx, None, None)?; + + Ok(MempoolInput { + tx: external_tx_to_thin_tx(&tx, tx_hash), + account: Account { address: get_sender_address(&tx), ..Default::default() }, }) } diff --git a/crates/gateway/src/gateway_test.rs b/crates/gateway/src/gateway_test.rs index 5949a824d78..a82dbcc4b6d 100644 --- a/crates/gateway/src/gateway_test.rs +++ b/crates/gateway/src/gateway_test.rs @@ -1,40 +1,83 @@ -use std::fs::File; -use std::path::Path; +use std::sync::Arc; +use assert_matches::assert_matches; use axum::body::{Bytes, HttpBody}; +use axum::extract::State; use axum::http::StatusCode; use axum::response::{IntoResponse, Response}; -use pretty_assertions::assert_str_eq; -use rstest::rstest; +use blockifier::context::ChainInfo; use starknet_api::external_transaction::ExternalTransaction; +use starknet_api::transaction::TransactionHash; +use starknet_mempool_types::mempool_types::{ + GatewayNetworkComponent, + GatewayToMempoolMessage, + MempoolToGatewayMessage, +}; +use tokio::sync::mpsc::channel; -use crate::gateway::add_transaction; +use crate::config::{StatefulTransactionValidatorConfig, StatelessTransactionValidatorConfig}; +use crate::gateway::{add_tx, AppState}; +use crate::starknet_api_test_utils::invoke_tx; +use crate::state_reader_test_utils::test_state_reader_factory; +use crate::stateful_transaction_validator::StatefulTransactionValidator; +use crate::stateless_transaction_validator::StatelessTransactionValidator; +use crate::utils::{external_tx_to_account_tx, get_tx_hash}; -const TEST_FILES_FOLDER: &str = "./tests/fixtures"; +pub fn app_state(network_component: GatewayNetworkComponent) -> AppState { + AppState { + stateless_tx_validator: StatelessTransactionValidator { + config: StatelessTransactionValidatorConfig { + validate_non_zero_l1_gas_fee: true, + max_calldata_length: 10, + max_signature_length: 2, + ..Default::default() + }, + }, + network_component: Arc::new(network_component), + stateful_tx_validator: Arc::new(StatefulTransactionValidator { + config: StatefulTransactionValidatorConfig::create_for_testing(), + }), + state_reader_factory: Arc::new(test_state_reader_factory()), + } +} -// TODO(Ayelet): Replace the use of the JSON files with generated instances, then serialize these -// into JSON for testing. -#[rstest] -#[case::declare(&Path::new(TEST_FILES_FOLDER).join("declare_v3.json"), "DECLARE")] -#[case::deploy_account( - &Path::new(TEST_FILES_FOLDER).join("deploy_account_v3.json"), - "DEPLOY_ACCOUNT" -)] -#[case::invoke(&Path::new(TEST_FILES_FOLDER).join("invoke_v3.json"), "INVOKE")] +// TODO(Ayelet): add test cases for declare and deploy account transactions. #[tokio::test] -async fn test_add_transaction(#[case] json_file_path: &Path, #[case] expected_response: &str) { - let json_file = File::open(json_file_path).unwrap(); - let tx: ExternalTransaction = serde_json::from_reader(json_file).unwrap(); +async fn test_add_tx() { + // The `_rx_gateway_to_mempool` is retained to keep the channel open, as dropping it would + // prevent the sender from transmitting messages. + let (tx_gateway_to_mempool, _rx_gateway_to_mempool) = channel::(1); + let (_, rx_mempool_to_gateway) = channel::(1); + // TODO: Add fixture. + let gateway_component = + GatewayNetworkComponent::new(tx_gateway_to_mempool, rx_mempool_to_gateway); + + let app_state = app_state(gateway_component); - let response = add_transaction(tx.into()).await.into_response(); + let tx = invoke_tx(); + let tx_hash = calculate_hash(&tx); + let response = add_tx(State(app_state), tx.into()).await.into_response(); let status_code = response.status(); assert_eq!(status_code, StatusCode::OK); let response_bytes = &to_bytes(response).await; - assert_str_eq!(&String::from_utf8_lossy(response_bytes), expected_response); + assert_eq!(tx_hash, serde_json::from_slice(response_bytes).unwrap()); } async fn to_bytes(res: Response) -> Bytes { res.into_body().collect().await.unwrap().to_bytes() } + +fn calculate_hash(external_tx: &ExternalTransaction) -> TransactionHash { + assert_matches!( + external_tx, + ExternalTransaction::Invoke(_), + "Only Invoke supported for now, extend as needed." + ); + + let account_tx = + external_tx_to_account_tx(external_tx, None, &ChainInfo::create_for_testing().chain_id) + .unwrap(); + get_tx_hash(&account_tx) +} diff --git a/crates/gateway/src/json_files_for_testing/gateway_config.json b/crates/gateway/src/json_files_for_testing/gateway_config.json deleted file mode 100644 index ff54280f56c..00000000000 --- a/crates/gateway/src/json_files_for_testing/gateway_config.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "ip": { - "description": "The gateway server ip.", - "value": "0.0.0.0", - "privacy": "Public" - }, - "port": { - "description": "The gateway server port.", - "value": 8080, - "privacy": "Public" - } -} \ No newline at end of file diff --git a/crates/gateway/src/lib.rs b/crates/gateway/src/lib.rs index 317ede01b8f..ceefe6b45ac 100644 --- a/crates/gateway/src/lib.rs +++ b/crates/gateway/src/lib.rs @@ -4,9 +4,8 @@ pub mod gateway; pub mod rpc_objects; pub mod rpc_state_reader; pub mod starknet_api_test_utils; +pub mod state_reader; +pub mod state_reader_test_utils; pub mod stateful_transaction_validator; pub mod stateless_transaction_validator; pub mod utils; - -#[cfg(test)] -mod config_test; diff --git a/crates/gateway/src/rpc_objects.rs b/crates/gateway/src/rpc_objects.rs index 9cb4de796bf..d713f208bfe 100644 --- a/crates/gateway/src/rpc_objects.rs +++ b/crates/gateway/src/rpc_objects.rs @@ -1,18 +1,30 @@ +use std::num::NonZeroU128; + +use blockifier::blockifier::block::{BlockInfo, GasPrices}; +use blockifier::state::errors::StateError; use serde::{Deserialize, Serialize}; use serde_json::Value; -use starknet_api::block::BlockNumber; -use starknet_api::core::ContractAddress; +use starknet_api::block::{BlockHash, BlockNumber, BlockTimestamp, GasPrice}; +use starknet_api::core::{ClassHash, ContractAddress, GlobalRoot}; +use starknet_api::data_availability::L1DataAvailabilityMode; +use starknet_api::state::StorageKey; // Starknet Spec error codes: // TODO(yael 30/4/2024): consider turning these into an enum. -pub const RPC_ERROR_BLOCK_NOT_FOUND: u16 = 24; pub const RPC_ERROR_CONTRACT_ADDRESS_NOT_FOUND: u16 = 20; +pub const RPC_ERROR_BLOCK_NOT_FOUND: u16 = 24; +pub const RPC_CLASS_HASH_NOT_FOUND: u16 = 28; -#[derive(Deserialize, Serialize)] +#[derive(Copy, Clone, Debug, Deserialize, Serialize)] pub enum BlockId { + #[serde(rename = "latest")] + Latest, + #[serde(rename = "pending")] + Pending, + #[serde(rename = "block_hash")] + Hash(BlockHash), #[serde(rename = "block_number")] Number(BlockNumber), - // There are additional options in the spec that are not implemented here } #[derive(Serialize, Deserialize)] @@ -21,6 +33,73 @@ pub struct GetNonceParams { pub contract_address: ContractAddress, } +#[derive(Serialize, Deserialize)] +pub struct GetStorageAtParams { + pub contract_address: ContractAddress, + pub key: StorageKey, + pub block_id: BlockId, +} + +#[derive(Serialize, Deserialize)] +pub struct GetClassHashAtParams { + pub contract_address: ContractAddress, + pub block_id: BlockId, +} + +#[derive(Serialize, Deserialize)] +pub struct GetCompiledContractClassParams { + pub class_hash: ClassHash, + pub block_id: BlockId, +} + +#[derive(Deserialize, Serialize)] +pub struct GetBlockWithTxHashesParams { + pub block_id: BlockId, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct ResourcePrice { + pub price_in_wei: GasPrice, + pub price_in_fri: GasPrice, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct BlockHeader { + pub block_hash: BlockHash, + pub parent_hash: BlockHash, + pub block_number: BlockNumber, + pub sequencer_address: ContractAddress, + pub new_root: GlobalRoot, + pub timestamp: BlockTimestamp, + pub l1_gas_price: ResourcePrice, + pub l1_data_gas_price: ResourcePrice, + pub l1_da_mode: L1DataAvailabilityMode, + pub starknet_version: String, +} + +impl TryInto for BlockHeader { + type Error = StateError; + fn try_into(self) -> Result { + Ok(BlockInfo { + block_number: self.block_number, + sequencer_address: self.sequencer_address, + block_timestamp: self.timestamp, + gas_prices: GasPrices { + eth_l1_gas_price: parse_gas_price(self.l1_gas_price.price_in_wei)?, + strk_l1_gas_price: parse_gas_price(self.l1_gas_price.price_in_fri)?, + eth_l1_data_gas_price: parse_gas_price(self.l1_data_gas_price.price_in_wei)?, + strk_l1_data_gas_price: parse_gas_price(self.l1_data_gas_price.price_in_fri)?, + }, + use_kzg_da: matches!(self.l1_da_mode, L1DataAvailabilityMode::Blob), + }) + } +} + +fn parse_gas_price(gas_price: GasPrice) -> Result { + NonZeroU128::new(gas_price.0) + .ok_or(StateError::StateReadError("Couldn't parse gas_price".to_string())) +} + #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] pub enum RpcResponse { diff --git a/crates/gateway/src/rpc_state_reader.rs b/crates/gateway/src/rpc_state_reader.rs index 7d0c4938a3c..5cb489d395d 100644 --- a/crates/gateway/src/rpc_state_reader.rs +++ b/crates/gateway/src/rpc_state_reader.rs @@ -1,41 +1,66 @@ -use blockifier::execution::contract_class::ContractClass; +use blockifier::blockifier::block::BlockInfo; +use blockifier::execution::contract_class::{ContractClass, ContractClassV1}; use blockifier::state::errors::StateError; -use blockifier::state::state_api::{StateReader, StateResult}; +use blockifier::state::state_api::{StateReader as BlockifierStateReader, StateResult}; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use reqwest::blocking::Client as BlockingClient; -use serde_json::{json, Value}; +use reqwest::Error as ReqwestError; +use serde::Serialize; +use serde_json::{json, Error as SerdeError, Value}; use starknet_api::block::BlockNumber; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::hash::StarkFelt; use starknet_api::state::StorageKey; -use url::Url; +use crate::config::RpcStateReaderConfig; use crate::rpc_objects::{ + BlockHeader, BlockId, + GetBlockWithTxHashesParams, + GetClassHashAtParams, + GetCompiledContractClassParams, GetNonceParams, + GetStorageAtParams, RpcResponse, + RPC_CLASS_HASH_NOT_FOUND, RPC_ERROR_BLOCK_NOT_FOUND, RPC_ERROR_CONTRACT_ADDRESS_NOT_FOUND, }; +use crate::state_reader::{MempoolStateReader, StateReaderFactory}; pub struct RpcStateReader { - pub url: Url, - pub json_rpc_version: String, - pub block_number: BlockNumber, + pub config: RpcStateReaderConfig, + pub block_id: BlockId, } impl RpcStateReader { + pub fn from_number(config: &RpcStateReaderConfig, block_number: BlockNumber) -> Self { + Self { config: config.clone(), block_id: BlockId::Number(block_number) } + } + pub fn from_latest(config: &RpcStateReaderConfig) -> Self { + Self { config: config.clone(), block_id: BlockId::Latest } + } // Note: This function is blocking though it is sending a request to the rpc server and waiting // for the response. - pub fn send_rpc_request(&self, request_body: serde_json::Value) -> Result { + pub fn send_rpc_request( + &self, + method: &str, + params: impl Serialize, + ) -> Result { + let request_body = json!({ + "jsonrpc": self.config.json_rpc_version, + "id": 0, + "method": method, + "params": json!(params), + }); + let client = BlockingClient::new(); let response = client - .post(self.url.clone()) + .post(self.config.url.clone()) .header("Content-Type", "application/json") .json(&request_body) .send() - .map_err(|e| { - StateError::StateReadError(format!("Rpc request failed with error {:?}", e)) - })?; + .map_err(reqwest_err_to_state_err)?; if !response.status().is_success() { return Err(StateError::StateReadError(format!( @@ -44,9 +69,8 @@ impl RpcStateReader { ))); } - let rpc_response: RpcResponse = response.json::().map_err(|e| { - StateError::StateReadError(format!("Couldn't parse json rpc response {}", e)) - })?; + let rpc_response: RpcResponse = + response.json::().map_err(reqwest_err_to_state_err)?; match rpc_response { RpcResponse::Success(rpc_success_response) => Ok(rpc_success_response.result), @@ -59,6 +83,10 @@ impl RpcStateReader { "Contract address not found, request: {}", request_body ))), + RPC_CLASS_HASH_NOT_FOUND => Err(StateError::StateReadError(format!( + "Class hash not found, request: {}", + request_body + ))), _ => Err(StateError::StateReadError(format!( "Unexpected error code {}", rpc_error_response.error.code @@ -68,44 +96,92 @@ impl RpcStateReader { } } -impl StateReader for RpcStateReader { - #[allow(unused_variables)] +impl MempoolStateReader for RpcStateReader { + fn get_block_info(&self) -> Result { + let get_block_params = GetBlockWithTxHashesParams { block_id: self.block_id }; + + // The response from the rpc is a full block but we only deserialize the header. + let block_header: BlockHeader = serde_json::from_value( + self.send_rpc_request("starknet_getBlockWithTxHashes", get_block_params)?, + ) + .map_err(serde_err_to_state_err)?; + let block_info = block_header.try_into()?; + Ok(block_info) + } +} + +impl BlockifierStateReader for RpcStateReader { fn get_storage_at( &self, contract_address: ContractAddress, key: StorageKey, ) -> StateResult { - todo!() + let get_storage_at_params = + GetStorageAtParams { block_id: self.block_id, contract_address, key }; + + let result = self.send_rpc_request("starknet_getStorageAt", get_storage_at_params)?; + let value: StarkFelt = serde_json::from_value(result).map_err(serde_err_to_state_err)?; + Ok(value) } fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult { - let get_nonce_params = - GetNonceParams { block_id: BlockId::Number(self.block_number), contract_address }; - let request_body = json!({ - "jsonrpc": self.json_rpc_version, - "id": 0, - "method": "starknet_getNonce", - "params": json!(get_nonce_params), - }); + let get_nonce_params = GetNonceParams { block_id: self.block_id, contract_address }; - let result = self.send_rpc_request(request_body)?; - let nonce: Nonce = serde_json::from_value(result) - .map_err(|_| StateError::StateReadError("Bad rpc result".to_string()))?; + let result = self.send_rpc_request("starknet_getNonce", get_nonce_params)?; + let nonce: Nonce = serde_json::from_value(result).map_err(serde_err_to_state_err)?; Ok(nonce) } - #[allow(unused_variables)] + // TODO(yael 12/5/24): currently only Cairo1 is supported, need to add support for Cairo0. fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { - todo!() + let get_compiled_class_params = + GetCompiledContractClassParams { class_hash, block_id: self.block_id }; + + let result = + self.send_rpc_request("starknet_getCompiledContractClass", get_compiled_class_params)?; + let casm_contract_class: CasmContractClass = + serde_json::from_value(result).map_err(serde_err_to_state_err)?; + let class_hash = ContractClass::V1( + ContractClassV1::try_from(casm_contract_class).map_err(StateError::ProgramError)?, + ); + Ok(class_hash) } - #[allow(unused_variables)] fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { - todo!() + let get_class_hash_at_params = + GetClassHashAtParams { contract_address, block_id: self.block_id }; + + let result = self.send_rpc_request("starknet_getClassHashAt", get_class_hash_at_params)?; + let class_hash: ClassHash = + serde_json::from_value(result).map_err(serde_err_to_state_err)?; + Ok(class_hash) } - #[allow(unused_variables)] - fn get_compiled_class_hash(&self, class_hash: ClassHash) -> StateResult { + fn get_compiled_class_hash(&self, _class_hash: ClassHash) -> StateResult { todo!() } } + +// Converts a serder error to the error type of the state reader. +fn serde_err_to_state_err(err: SerdeError) -> StateError { + StateError::StateReadError(format!("Failed to parse rpc result {:?}", err.to_string())) +} + +// Converts a reqwest error to the error type of the state reader. +fn reqwest_err_to_state_err(err: ReqwestError) -> StateError { + StateError::StateReadError(format!("Rpc request failed with error {:?}", err.to_string())) +} + +pub struct RpcStateReaderFactory { + config: RpcStateReaderConfig, +} + +impl StateReaderFactory for RpcStateReaderFactory { + fn get_state_reader_from_latest_block(&self) -> Box { + Box::new(RpcStateReader::from_latest(&self.config)) + } + + fn get_state_reader(&self, block_number: BlockNumber) -> Box { + Box::new(RpcStateReader::from_number(&self.config, block_number)) + } +} diff --git a/crates/gateway/src/starknet_api_test_utils.rs b/crates/gateway/src/starknet_api_test_utils.rs index 831c950be8b..6a916aa51c5 100644 --- a/crates/gateway/src/starknet_api_test_utils.rs +++ b/crates/gateway/src/starknet_api_test_utils.rs @@ -1,6 +1,11 @@ -use starknet_api::core::{ContractAddress, Nonce}; +use blockifier::test_utils::contracts::FeatureContract; +use blockifier::test_utils::{create_trivial_calldata, CairoVersion, NonceManager}; +use serde_json::to_string_pretty; +use starknet_api::calldata; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::external_transaction::{ + ContractClass, ExternalDeclareTransaction, ExternalDeclareTransactionV3, ExternalDeployAccountTransaction, @@ -8,15 +13,23 @@ use starknet_api::external_transaction::{ ExternalInvokeTransaction, ExternalInvokeTransactionV3, ExternalTransaction, + ResourceBoundsMapping, }; +use starknet_api::hash::StarkFelt; use starknet_api::transaction::{ + AccountDeploymentData, Calldata, + ContractAddressSalt, + PaymasterData, ResourceBounds, - ResourceBoundsMapping, + Tip, TransactionSignature, + TransactionVersion, }; -pub const VALID_L1_GAS_MAX_AMOUNT: u64 = 1662; +use crate::{declare_tx_args, deploy_account_tx_args, invoke_tx_args}; + +pub const VALID_L1_GAS_MAX_AMOUNT: u64 = 2214; pub const VALID_L1_GAS_MAX_PRICE_PER_UNIT: u128 = 100000000000; // Utils. @@ -26,102 +39,34 @@ pub enum TransactionType { Invoke, } -pub fn external_tx_for_testing( - transaction_type: TransactionType, - resource_bounds: ResourceBoundsMapping, - calldata: Option, - signature: TransactionSignature, -) -> ExternalTransaction { - match transaction_type { - TransactionType::Declare => external_declare_tx_for_testing(resource_bounds, signature), - TransactionType::DeployAccount => external_deploy_account_tx_for_testing( - resource_bounds, - calldata.expect("Calldata is missing."), - signature, - ), - TransactionType::Invoke => external_invoke_tx_for_testing( - resource_bounds, - calldata.expect("Calldata is missing."), - signature, - ), +pub fn get_sender_address(tx: &ExternalTransaction) -> ContractAddress { + match tx { + ExternalTransaction::Declare(ExternalDeclareTransaction::V3(tx)) => tx.sender_address, + // TODO(Mohammad): Add support for deploy account. + ExternalTransaction::DeployAccount(ExternalDeployAccountTransaction::V3(_)) => { + ContractAddress::default() + } + ExternalTransaction::Invoke(ExternalInvokeTransaction::V3(tx)) => tx.sender_address, } } -fn external_declare_tx_for_testing( - resource_bounds: ResourceBoundsMapping, - signature: TransactionSignature, -) -> ExternalTransaction { - ExternalTransaction::Declare(ExternalDeclareTransaction::V3(ExternalDeclareTransactionV3 { - resource_bounds, - contract_class: Default::default(), - tip: Default::default(), - signature, - nonce: Default::default(), - compiled_class_hash: Default::default(), - sender_address: Default::default(), - nonce_data_availability_mode: DataAvailabilityMode::L1, - fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: Default::default(), - account_deployment_data: Default::default(), - })) -} - -fn external_deploy_account_tx_for_testing( - resource_bounds: ResourceBoundsMapping, - constructor_calldata: Calldata, - signature: TransactionSignature, -) -> ExternalTransaction { - ExternalTransaction::DeployAccount(ExternalDeployAccountTransaction::V3( - ExternalDeployAccountTransactionV3 { - resource_bounds, - tip: Default::default(), - contract_address_salt: Default::default(), - class_hash: Default::default(), - constructor_calldata, - nonce: Default::default(), - signature, - nonce_data_availability_mode: DataAvailabilityMode::L1, - fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: Default::default(), - }, - )) -} - -fn external_invoke_tx_for_testing( - resource_bounds: ResourceBoundsMapping, - calldata: Calldata, - signature: TransactionSignature, -) -> ExternalTransaction { - executable_external_invoke_tx_for_testing( - resource_bounds, - Nonce::default(), - ContractAddress::default(), - calldata, - signature, - ) -} - -// TODO(yael 24/4/24): remove this function and generalize the external_tx_for_testing function. -// and add a struct for default args (ExteranlTransactionForTestingArgs) -pub fn executable_external_invoke_tx_for_testing( +pub fn external_tx_for_testing( + tx_type: TransactionType, resource_bounds: ResourceBoundsMapping, - nonce: Nonce, - sender_address: ContractAddress, calldata: Calldata, signature: TransactionSignature, ) -> ExternalTransaction { - ExternalTransaction::Invoke(ExternalInvokeTransaction::V3(ExternalInvokeTransactionV3 { - resource_bounds, - tip: Default::default(), - signature, - nonce, - sender_address, - calldata, - nonce_data_availability_mode: DataAvailabilityMode::L1, - fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: Default::default(), - account_deployment_data: Default::default(), - })) + match tx_type { + TransactionType::Declare => { + external_declare_tx(declare_tx_args!(resource_bounds, signature)) + } + TransactionType::DeployAccount => external_deploy_account_tx( + deploy_account_tx_args!(resource_bounds, constructor_calldata: calldata, signature), + ), + TransactionType::Invoke => { + external_invoke_tx(invoke_tx_args!(signature, resource_bounds, calldata)) + } + } } pub const NON_EMPTY_RESOURCE_BOUNDS: ResourceBounds = @@ -131,11 +76,7 @@ pub fn create_resource_bounds_mapping( l1_resource_bounds: ResourceBounds, l2_resource_bounds: ResourceBounds, ) -> ResourceBoundsMapping { - ResourceBoundsMapping::try_from(vec![ - (starknet_api::transaction::Resource::L1Gas, l1_resource_bounds), - (starknet_api::transaction::Resource::L2Gas, l2_resource_bounds), - ]) - .expect("Resource bounds mapping has unexpected structure.") + ResourceBoundsMapping { l1_gas: l1_resource_bounds, l2_gas: l2_resource_bounds } } pub fn zero_resource_bounds_mapping() -> ResourceBoundsMapping { @@ -143,19 +84,278 @@ pub fn zero_resource_bounds_mapping() -> ResourceBoundsMapping { } pub fn non_zero_resource_bounds_mapping() -> ResourceBoundsMapping { - create_resource_bounds_mapping(NON_EMPTY_RESOURCE_BOUNDS, NON_EMPTY_RESOURCE_BOUNDS) + ResourceBoundsMapping { l1_gas: NON_EMPTY_RESOURCE_BOUNDS, l2_gas: NON_EMPTY_RESOURCE_BOUNDS } } pub fn executable_resource_bounds_mapping() -> ResourceBoundsMapping { - ResourceBoundsMapping::try_from(vec![ - ( - starknet_api::transaction::Resource::L1Gas, - ResourceBounds { - max_amount: VALID_L1_GAS_MAX_AMOUNT, - max_price_per_unit: VALID_L1_GAS_MAX_PRICE_PER_UNIT, - }, - ), - (starknet_api::transaction::Resource::L2Gas, ResourceBounds::default()), - ]) - .expect("Resource bounds mapping has unexpected structure.") + ResourceBoundsMapping { + l1_gas: ResourceBounds { + max_amount: VALID_L1_GAS_MAX_AMOUNT, + max_price_per_unit: VALID_L1_GAS_MAX_PRICE_PER_UNIT, + }, + l2_gas: ResourceBounds::default(), + } +} + +pub fn invoke_tx() -> ExternalTransaction { + let cairo_version = CairoVersion::Cairo1; + let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); + let account_address = account_contract.get_instance_address(0); + let test_contract = FeatureContract::TestContract(cairo_version); + let test_contract_address = test_contract.get_instance_address(0); + let calldata = create_trivial_calldata(test_contract_address); + let mut nonce_manager = NonceManager::default(); + let nonce = nonce_manager.next(account_address); + external_invoke_tx(invoke_tx_args!( + signature: TransactionSignature(vec![StarkFelt::ZERO]), + sender_address: account_address, + resource_bounds: executable_resource_bounds_mapping(), + nonce, + calldata, + )) +} +// TODO(Ayelet, 28/5/2025): Try unifying the macros. +// TODO(Ayelet, 28/5/2025): Consider moving the macros StarkNet API. +#[macro_export] +macro_rules! invoke_tx_args { + ($($field:ident $(: $value:expr)?),* $(,)?) => { + $crate::starknet_api_test_utils::InvokeTxArgs { + $($field $(: $value)?,)* + ..Default::default() + } + }; + ($($field:ident $(: $value:expr)?),* , ..$defaults:expr) => { + $crate::starknet_api_test_utils::InvokeTxArgs { + $($field $(: $value)?,)* + ..$defaults + } + }; +} + +#[macro_export] +macro_rules! deploy_account_tx_args { + ($($field:ident $(: $value:expr)?),* $(,)?) => { + $crate::starknet_api_test_utils::DeployAccountTxArgs { + $($field $(: $value)?,)* + ..Default::default() + } + }; + ($($field:ident $(: $value:expr)?),* , ..$defaults:expr) => { + $crate::starknet_api_test_utils::DeployAccountTxArgs { + $($field $(: $value)?,)* + ..$defaults + } + }; +} + +#[macro_export] +macro_rules! declare_tx_args { + ($($field:ident $(: $value:expr)?),* $(,)?) => { + $crate::starknet_api_test_utils::DeclareTxArgs { + $($field $(: $value)?,)* + ..Default::default() + } + }; + ($($field:ident $(: $value:expr)?),* , ..$defaults:expr) => { + $crate::starknet_api_test_utils::DeclareTxArgs { + $($field $(: $value)?,)* + ..$defaults + } + }; +} + +#[derive(Clone)] +pub struct InvokeTxArgs { + pub signature: TransactionSignature, + pub sender_address: ContractAddress, + pub calldata: Calldata, + pub version: TransactionVersion, + pub resource_bounds: ResourceBoundsMapping, + pub tip: Tip, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + pub account_deployment_data: AccountDeploymentData, + pub nonce: Nonce, +} + +impl Default for InvokeTxArgs { + fn default() -> Self { + InvokeTxArgs { + signature: TransactionSignature::default(), + sender_address: ContractAddress::default(), + calldata: calldata![], + version: TransactionVersion::THREE, + resource_bounds: zero_resource_bounds_mapping(), + tip: Tip::default(), + nonce_data_availability_mode: DataAvailabilityMode::L1, + fee_data_availability_mode: DataAvailabilityMode::L1, + paymaster_data: PaymasterData::default(), + account_deployment_data: AccountDeploymentData::default(), + nonce: Nonce::default(), + } + } +} + +#[derive(Clone)] +pub struct DeployAccountTxArgs { + pub signature: TransactionSignature, + pub deployer_address: ContractAddress, + pub version: TransactionVersion, + pub resource_bounds: ResourceBoundsMapping, + pub tip: Tip, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + pub nonce: Nonce, + pub class_hash: ClassHash, + pub contract_address_salt: ContractAddressSalt, + pub constructor_calldata: Calldata, +} + +impl Default for DeployAccountTxArgs { + fn default() -> Self { + DeployAccountTxArgs { + signature: TransactionSignature::default(), + deployer_address: ContractAddress::default(), + version: TransactionVersion::THREE, + resource_bounds: zero_resource_bounds_mapping(), + tip: Tip::default(), + nonce_data_availability_mode: DataAvailabilityMode::L1, + fee_data_availability_mode: DataAvailabilityMode::L1, + paymaster_data: PaymasterData::default(), + nonce: Nonce::default(), + class_hash: ClassHash::default(), + contract_address_salt: ContractAddressSalt::default(), + constructor_calldata: Calldata::default(), + } + } +} + +#[derive(Clone)] +pub struct DeclareTxArgs { + pub signature: TransactionSignature, + pub sender_address: ContractAddress, + pub version: TransactionVersion, + pub resource_bounds: ResourceBoundsMapping, + pub tip: Tip, + pub nonce_data_availability_mode: DataAvailabilityMode, + pub fee_data_availability_mode: DataAvailabilityMode, + pub paymaster_data: PaymasterData, + pub account_deployment_data: AccountDeploymentData, + pub nonce: Nonce, + pub class_hash: CompiledClassHash, + pub contract_class: ContractClass, +} + +impl Default for DeclareTxArgs { + fn default() -> Self { + Self { + signature: TransactionSignature::default(), + sender_address: ContractAddress::default(), + version: TransactionVersion::THREE, + resource_bounds: zero_resource_bounds_mapping(), + tip: Tip::default(), + nonce_data_availability_mode: DataAvailabilityMode::L1, + fee_data_availability_mode: DataAvailabilityMode::L1, + paymaster_data: PaymasterData::default(), + account_deployment_data: AccountDeploymentData::default(), + nonce: Nonce::default(), + class_hash: CompiledClassHash::default(), + contract_class: ContractClass::default(), + } + } +} + +pub fn external_invoke_tx(invoke_args: InvokeTxArgs) -> ExternalTransaction { + match invoke_args.version { + TransactionVersion::THREE => { + starknet_api::external_transaction::ExternalTransaction::Invoke( + starknet_api::external_transaction::ExternalInvokeTransaction::V3( + ExternalInvokeTransactionV3 { + resource_bounds: invoke_args.resource_bounds, + tip: invoke_args.tip, + calldata: invoke_args.calldata, + sender_address: invoke_args.sender_address, + nonce: invoke_args.nonce, + signature: invoke_args.signature, + nonce_data_availability_mode: invoke_args.nonce_data_availability_mode, + fee_data_availability_mode: invoke_args.fee_data_availability_mode, + paymaster_data: invoke_args.paymaster_data, + account_deployment_data: invoke_args.account_deployment_data, + }, + ), + ) + } + _ => panic!("Unsupported transaction version: {:?}.", invoke_args.version), + } +} + +pub fn external_deploy_account_tx(deploy_tx_args: DeployAccountTxArgs) -> ExternalTransaction { + match deploy_tx_args.version { + TransactionVersion::THREE => { + starknet_api::external_transaction::ExternalTransaction::DeployAccount( + starknet_api::external_transaction::ExternalDeployAccountTransaction::V3( + ExternalDeployAccountTransactionV3 { + resource_bounds: deploy_tx_args.resource_bounds, + tip: deploy_tx_args.tip, + contract_address_salt: deploy_tx_args.contract_address_salt, + class_hash: deploy_tx_args.class_hash, + constructor_calldata: deploy_tx_args.constructor_calldata, + nonce: deploy_tx_args.nonce, + signature: deploy_tx_args.signature, + nonce_data_availability_mode: deploy_tx_args.nonce_data_availability_mode, + fee_data_availability_mode: deploy_tx_args.fee_data_availability_mode, + paymaster_data: deploy_tx_args.paymaster_data, + }, + ), + ) + } + _ => panic!("Unsupported transaction version: {:?}.", deploy_tx_args.version), + } +} + +pub fn external_declare_tx(declare_tx_args: DeclareTxArgs) -> ExternalTransaction { + match declare_tx_args.version { + TransactionVersion::THREE => { + starknet_api::external_transaction::ExternalTransaction::Declare( + starknet_api::external_transaction::ExternalDeclareTransaction::V3( + ExternalDeclareTransactionV3 { + contract_class: declare_tx_args.contract_class, + signature: declare_tx_args.signature, + sender_address: declare_tx_args.sender_address, + resource_bounds: declare_tx_args.resource_bounds, + tip: declare_tx_args.tip, + nonce_data_availability_mode: declare_tx_args.nonce_data_availability_mode, + fee_data_availability_mode: declare_tx_args.fee_data_availability_mode, + paymaster_data: declare_tx_args.paymaster_data, + account_deployment_data: declare_tx_args.account_deployment_data, + nonce: declare_tx_args.nonce, + compiled_class_hash: declare_tx_args.class_hash, + }, + ), + ) + } + _ => panic!("Unsupported transaction version: {:?}.", declare_tx_args.version), + } +} + +pub fn external_tx_to_json(tx: &ExternalTransaction) -> String { + let mut tx_json = serde_json::to_value(tx) + .unwrap_or_else(|tx| panic!("Failed to serialize transaction: {tx:?}")); + + // Add type and version manually + let type_string = match tx { + ExternalTransaction::Declare(_) => "DECLARE", + ExternalTransaction::DeployAccount(_) => "DEPLOY_ACCOUNT", + ExternalTransaction::Invoke(_) => "INVOKE", + }; + + tx_json + .as_object_mut() + .unwrap() + .extend([("type".to_string(), type_string.into()), ("version".to_string(), "0x3".into())]); + + // Serialize back to pretty JSON string + to_string_pretty(&tx_json).expect("Failed to serialize transaction") } diff --git a/crates/gateway/src/state_reader.rs b/crates/gateway/src/state_reader.rs new file mode 100644 index 00000000000..1833bc29a80 --- /dev/null +++ b/crates/gateway/src/state_reader.rs @@ -0,0 +1,52 @@ +use blockifier::blockifier::block::BlockInfo; +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{StateReader as BlockifierStateReader, StateResult}; +use starknet_api::block::BlockNumber; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +pub trait MempoolStateReader: BlockifierStateReader + Send + Sync { + fn get_block_info(&self) -> Result; +} + +pub trait StateReaderFactory: Send + Sync { + fn get_state_reader_from_latest_block(&self) -> Box; + fn get_state_reader(&self, block_number: BlockNumber) -> Box; +} + +// By default, a Box does not implement the trait of the object it contains. +// Therefore, for using the Box, that the StateReaderFactory creates, +// we need to implement the MempoolStateReader trait for Box. +impl MempoolStateReader for Box { + fn get_block_info(&self) -> Result { + self.as_ref().get_block_info() + } +} + +impl BlockifierStateReader for Box { + fn get_storage_at( + &self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.as_ref().get_storage_at(contract_address, key) + } + + fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult { + self.as_ref().get_nonce_at(contract_address) + } + + fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { + self.as_ref().get_class_hash_at(contract_address) + } + + fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { + self.as_ref().get_compiled_contract_class(class_hash) + } + + fn get_compiled_class_hash(&self, class_hash: ClassHash) -> StateResult { + self.as_ref().get_compiled_class_hash(class_hash) + } +} diff --git a/crates/gateway/src/state_reader_test_utils.rs b/crates/gateway/src/state_reader_test_utils.rs new file mode 100644 index 00000000000..5cbcdbd2e43 --- /dev/null +++ b/crates/gateway/src/state_reader_test_utils.rs @@ -0,0 +1,87 @@ +use blockifier::blockifier::block::BlockInfo; +use blockifier::context::BlockContext; +use blockifier::execution::contract_class::ContractClass; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{StateReader as BlockifierStateReader, StateResult}; +use blockifier::test_utils::contracts::FeatureContract; +use blockifier::test_utils::dict_state_reader::DictStateReader; +use blockifier::test_utils::initial_test_state::test_state_reader; +use blockifier::test_utils::{CairoVersion, BALANCE}; +use starknet_api::block::BlockNumber; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +use crate::state_reader::{MempoolStateReader, StateReaderFactory}; + +#[derive(Clone)] +pub struct TestStateReader { + pub block_info: BlockInfo, + pub blockifier_state_reader: DictStateReader, +} + +impl MempoolStateReader for TestStateReader { + fn get_block_info(&self) -> Result { + Ok(self.block_info.clone()) + } +} + +impl BlockifierStateReader for TestStateReader { + fn get_storage_at( + &self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + self.blockifier_state_reader.get_storage_at(contract_address, key) + } + + fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult { + self.blockifier_state_reader.get_nonce_at(contract_address) + } + + fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { + self.blockifier_state_reader.get_class_hash_at(contract_address) + } + + fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { + self.blockifier_state_reader.get_compiled_contract_class(class_hash) + } + + fn get_compiled_class_hash(&self, class_hash: ClassHash) -> StateResult { + self.blockifier_state_reader.get_compiled_class_hash(class_hash) + } +} + +pub struct TestStateReaderFactory { + pub state_reader: TestStateReader, +} + +impl StateReaderFactory for TestStateReaderFactory { + fn get_state_reader_from_latest_block(&self) -> Box { + Box::new(self.state_reader.clone()) + } + + fn get_state_reader(&self, _block_number: BlockNumber) -> Box { + Box::new(self.state_reader.clone()) + } +} + +pub fn test_state_reader_factory() -> TestStateReaderFactory { + let cairo_version = CairoVersion::Cairo1; + let block_context = &BlockContext::create_for_testing(); + let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); + let test_contract = FeatureContract::TestContract(cairo_version); + + let state_reader = test_state_reader( + block_context.chain_info(), + BALANCE, + &[(account_contract, 1), (test_contract, 1)], + ); + + TestStateReaderFactory { + state_reader: TestStateReader { + block_info: block_context.block_info().clone(), + blockifier_state_reader: state_reader, + }, + } +} diff --git a/crates/gateway/src/stateful_transaction_validator.rs b/crates/gateway/src/stateful_transaction_validator.rs index 1a32439ed47..8cb7fad376e 100644 --- a/crates/gateway/src/stateful_transaction_validator.rs +++ b/crates/gateway/src/stateful_transaction_validator.rs @@ -1,17 +1,17 @@ use blockifier::blockifier::block::BlockInfo; use blockifier::blockifier::stateful_validator::StatefulValidator as BlockifierStatefulValidator; use blockifier::bouncer::BouncerConfig; -use blockifier::context::{BlockContext, ChainInfo}; +use blockifier::context::BlockContext; use blockifier::execution::contract_class::ClassInfo; use blockifier::state::cached_state::CachedState; -use blockifier::state::state_api::StateReader; use blockifier::versioned_constants::VersionedConstants; -use starknet_api::core::Nonce; use starknet_api::external_transaction::ExternalTransaction; use starknet_api::transaction::TransactionHash; +use crate::config::StatefulTransactionValidatorConfig; use crate::errors::{StatefulTransactionValidatorError, StatefulTransactionValidatorResult}; -use crate::utils::external_tx_to_account_tx; +use crate::state_reader::{MempoolStateReader, StateReaderFactory}; +use crate::utils::{external_tx_to_account_tx, get_tx_hash}; #[cfg(test)] #[path = "stateful_transaction_validator_test.rs"] @@ -24,15 +24,15 @@ pub struct StatefulTransactionValidator { impl StatefulTransactionValidator { pub fn run_validate( &self, - // TODO(yael 17/4/24): the state_reader should be created inside the function taking - // latest_block_number. - state_reader: impl StateReader, - // TODO(yael 17/4/24): the latest_block_info should be read from the storage. - latest_block_info: BlockInfo, + state_reader_factory: &dyn StateReaderFactory, external_tx: &ExternalTransaction, - deploy_account_tx_hash: Option, optional_class_info: Option, - ) -> StatefulTransactionValidatorResult<()> { + deploy_account_tx_hash: Option, + ) -> StatefulTransactionValidatorResult { + // TODO(yael 6/5/2024): consider storing the block_info as part of the + // StatefulTransactionValidator and update it only once a new block is created. + let latest_block_info = get_latest_block_info(state_reader_factory)?; + let state_reader = state_reader_factory.get_state_reader(latest_block_info.block_number); let state = CachedState::new(state_reader); let versioned_constants = VersionedConstants::latest_constants_with_overrides( self.config.validate_max_n_steps, @@ -46,8 +46,11 @@ impl StatefulTransactionValidator { )?; // TODO(yael 21/4/24): create the block context using pre_process_block once we will be // able to read the block_hash of 10 blocks ago from papyrus. - let block_context = - BlockContext::new_unchecked(&block_info, &self.config.chain_info, &versioned_constants); + let block_context = BlockContext::new_unchecked( + &block_info, + &self.config.chain_info.clone().into(), + &versioned_constants, + ); let mut validator = BlockifierStatefulValidator::create( state, @@ -60,14 +63,15 @@ impl StatefulTransactionValidator { optional_class_info, &self.config.chain_info.chain_id, )?; + let tx_hash = get_tx_hash(&account_tx); validator.perform_validations(account_tx, deploy_account_tx_hash)?; - Ok(()) + Ok(tx_hash) } } -pub struct StatefulTransactionValidatorConfig { - pub max_nonce_for_validation_skip: Nonce, - pub validate_max_n_steps: u32, - pub max_recursion_depth: usize, - pub chain_info: ChainInfo, +pub fn get_latest_block_info( + state_reader_factory: &dyn StateReaderFactory, +) -> StatefulTransactionValidatorResult { + let state_reader = state_reader_factory.get_state_reader_from_latest_block(); + Ok(state_reader.get_block_info()?) } diff --git a/crates/gateway/src/stateful_transaction_validator_test.rs b/crates/gateway/src/stateful_transaction_validator_test.rs index da417a01f34..6913b6cd84a 100644 --- a/crates/gateway/src/stateful_transaction_validator_test.rs +++ b/crates/gateway/src/stateful_transaction_validator_test.rs @@ -6,20 +6,26 @@ use blockifier::test_utils::{create_trivial_calldata, CairoVersion, NonceManager use blockifier::transaction::errors::{TransactionFeeError, TransactionPreValidationError}; use rstest::rstest; use starknet_api::hash::StarkFelt; +use starknet_api::transaction::TransactionHash; -use super::{StatefulTransactionValidator, StatefulTransactionValidatorConfig}; +use crate::config::StatefulTransactionValidatorConfig; use crate::errors::{StatefulTransactionValidatorError, StatefulTransactionValidatorResult}; +use crate::invoke_tx_args; use crate::starknet_api_test_utils::{ - executable_external_invoke_tx_for_testing, executable_resource_bounds_mapping, + external_invoke_tx, VALID_L1_GAS_MAX_AMOUNT, VALID_L1_GAS_MAX_PRICE_PER_UNIT, }; +use crate::state_reader_test_utils::{TestStateReader, TestStateReaderFactory}; +use crate::stateful_transaction_validator::StatefulTransactionValidator; #[rstest] #[case::valid_invoke_tx( 100000000000000000, - Ok(()) + Ok(TransactionHash(StarkFelt::try_from( + "0x07459d76bd7adec02c25cf7ab0dcb95e9197101d4ada41cae6b465fcb78c0e47" + ).unwrap())) )] #[case::invalid_invoke_tx( 0, @@ -36,14 +42,14 @@ use crate::starknet_api_test_utils::{ ) )) )] -fn test_stateful_transaction_validator( +fn test_stateful_tx_validator( #[case] account_balance: u128, - #[case] expected_result: StatefulTransactionValidatorResult<()>, + #[case] expected_result: StatefulTransactionValidatorResult, ) { let cairo_version = CairoVersion::Cairo1; let block_context = &BlockContext::create_for_testing(); let account_contract = FeatureContract::AccountWithoutValidations(cairo_version); - let account_address = account_contract.get_instance_address(0); + let sender_address = account_contract.get_instance_address(0); let test_contract = FeatureContract::TestContract(cairo_version); let test_contract_address = test_contract.get_instance_address(0); @@ -53,32 +59,31 @@ fn test_stateful_transaction_validator( &[(account_contract, 1), (test_contract, 1)], ); + let state_reader_factory = TestStateReaderFactory { + state_reader: TestStateReader { + block_info: block_context.block_info().clone(), + blockifier_state_reader: state_reader, + }, + }; + let stateful_validator = StatefulTransactionValidator { config: StatefulTransactionValidatorConfig { max_nonce_for_validation_skip: Default::default(), validate_max_n_steps: block_context.versioned_constants().validate_max_n_steps, max_recursion_depth: block_context.versioned_constants().max_recursion_depth, - chain_info: block_context.chain_info().clone(), + chain_info: block_context.chain_info().clone().into(), }, }; let calldata = create_trivial_calldata(test_contract_address); let mut nonce_manager = NonceManager::default(); - let nonce = nonce_manager.next(account_address); - let external_tx = executable_external_invoke_tx_for_testing( - executable_resource_bounds_mapping(), + let nonce = nonce_manager.next(sender_address); + let external_tx = external_invoke_tx(invoke_tx_args!( + resource_bounds: executable_resource_bounds_mapping(), nonce, - account_address, - calldata, - Default::default(), - ); + sender_address, + calldata)); - let result = stateful_validator.run_validate( - state_reader, - block_context.block_info().clone(), - &external_tx, - None, - None, - ); + let result = stateful_validator.run_validate(&state_reader_factory, &external_tx, None, None); assert_eq!(format!("{:?}", result), format!("{:?}", expected_result)); } diff --git a/crates/gateway/src/stateless_transaction_validator.rs b/crates/gateway/src/stateless_transaction_validator.rs index 28a97babb02..4fd777d820e 100644 --- a/crates/gateway/src/stateless_transaction_validator.rs +++ b/crates/gateway/src/stateless_transaction_validator.rs @@ -2,26 +2,18 @@ use starknet_api::external_transaction::{ ExternalDeployAccountTransaction, ExternalInvokeTransaction, ExternalTransaction, + ResourceBoundsMapping, }; -use starknet_api::transaction::{Resource, ResourceBoundsMapping}; +use starknet_api::transaction::Resource; +use crate::config::StatelessTransactionValidatorConfig; use crate::errors::{StatelessTransactionValidatorError, StatelessTransactionValidatorResult}; -use crate::utils::ExternalTransactionExt; #[cfg(test)] #[path = "stateless_transaction_validator_test.rs"] mod stateless_transaction_validator_test; -#[derive(Default)] -pub struct StatelessTransactionValidatorConfig { - // If true, validates that the resource bounds are not zero. - pub validate_non_zero_l1_gas_fee: bool, - pub validate_non_zero_l2_gas_fee: bool, - - pub max_calldata_length: usize, - pub max_signature_length: usize, -} - +#[derive(Clone)] pub struct StatelessTransactionValidator { pub config: StatelessTransactionValidatorConfig, } @@ -113,15 +105,15 @@ fn validate_resource_is_non_zero( resource_bounds_mapping: &ResourceBoundsMapping, resource: Resource, ) -> StatelessTransactionValidatorResult<()> { - if let Some(resource_bounds) = resource_bounds_mapping.0.get(&resource) { - if resource_bounds.max_amount == 0 || resource_bounds.max_price_per_unit == 0 { - return Err(StatelessTransactionValidatorError::ZeroResourceBounds { - resource, - resource_bounds: *resource_bounds, - }); - } - } else { - return Err(StatelessTransactionValidatorError::MissingResource { resource }); + let resource_bounds = match resource { + Resource::L1Gas => resource_bounds_mapping.l1_gas, + Resource::L2Gas => resource_bounds_mapping.l2_gas, + }; + if resource_bounds.max_amount == 0 || resource_bounds.max_price_per_unit == 0 { + return Err(StatelessTransactionValidatorError::ZeroResourceBounds { + resource, + resource_bounds, + }); } Ok(()) diff --git a/crates/gateway/src/stateless_transaction_validator_test.rs b/crates/gateway/src/stateless_transaction_validator_test.rs index 9cb1f34b53c..ee9cdb19294 100644 --- a/crates/gateway/src/stateless_transaction_validator_test.rs +++ b/crates/gateway/src/stateless_transaction_validator_test.rs @@ -1,13 +1,9 @@ +use assert_matches::assert_matches; use rstest::rstest; use starknet_api::calldata; +use starknet_api::external_transaction::ResourceBoundsMapping; use starknet_api::hash::StarkFelt; -use starknet_api::transaction::{ - Calldata, - Resource, - ResourceBounds, - ResourceBoundsMapping, - TransactionSignature, -}; +use starknet_api::transaction::{Calldata, Resource, ResourceBounds, TransactionSignature}; use crate::starknet_api_test_utils::{ create_resource_bounds_mapping, @@ -80,30 +76,12 @@ fn test_positive_flow( tx_type: TransactionType, ) { let tx_validator = StatelessTransactionValidator { config }; - let tx = external_tx_for_testing(tx_type, resource_bounds, Some(tx_calldata), signature); + let tx = external_tx_for_testing(tx_type, resource_bounds, tx_calldata, signature); - assert!(tx_validator.validate(&tx).is_ok()); + assert_matches!(tx_validator.validate(&tx), Ok(())); } #[rstest] -#[case::missing_l1_gas_resource_bounds( - StatelessTransactionValidatorConfig { - validate_non_zero_l1_gas_fee: true, - validate_non_zero_l2_gas_fee: false, - ..DEFAULT_VALIDATOR_CONFIG_FOR_TESTING - }, - ResourceBoundsMapping::default(), - StatelessTransactionValidatorError::MissingResource { resource: Resource::L1Gas } -)] -#[case::missing_l2_gas_resource_bounds( - StatelessTransactionValidatorConfig { - validate_non_zero_l1_gas_fee: false, - validate_non_zero_l2_gas_fee: true, - ..DEFAULT_VALIDATOR_CONFIG_FOR_TESTING - }, - ResourceBoundsMapping::default(), - StatelessTransactionValidatorError::MissingResource { resource: Resource::L2Gas } -)] #[case::zero_l1_gas_resource_bounds( DEFAULT_VALIDATOR_CONFIG_FOR_TESTING, zero_resource_bounds_mapping(), @@ -129,7 +107,7 @@ fn test_invalid_resource_bounds( let tx = external_tx_for_testing( tx_type, resource_bounds, - Some(calldata![]), + calldata![], TransactionSignature::default(), ); @@ -145,7 +123,7 @@ fn test_calldata_too_long( let tx = external_tx_for_testing( tx_type, non_zero_resource_bounds_mapping(), - Some(calldata![StarkFelt::from_u128(1), StarkFelt::from_u128(2)]), + calldata![StarkFelt::from_u128(1), StarkFelt::from_u128(2)], TransactionSignature::default(), ); @@ -168,7 +146,7 @@ fn test_signature_too_long( let tx = external_tx_for_testing( tx_type, non_zero_resource_bounds_mapping(), - Some(calldata![]), + calldata![], TransactionSignature(vec![StarkFelt::from_u128(1), StarkFelt::from_u128(2)]), ); diff --git a/crates/gateway/src/utils.rs b/crates/gateway/src/utils.rs index 46e82878acc..d29150c4c99 100644 --- a/crates/gateway/src/utils.rs +++ b/crates/gateway/src/utils.rs @@ -5,7 +5,7 @@ use blockifier::transaction::transactions::{ DeployAccountTransaction as BlockifierDeployAccountTransaction, InvokeTransaction as BlockifierInvokeTransaction, }; -use starknet_api::core::{calculate_contract_address, ChainId, ClassHash, ContractAddress}; +use starknet_api::core::{calculate_contract_address, ChainId, ClassHash, ContractAddress, Nonce}; use starknet_api::external_transaction::{ ExternalDeclareTransaction, ExternalDeployAccountTransaction, @@ -19,12 +19,14 @@ use starknet_api::transaction::{ DeployAccountTransactionV3, InvokeTransaction, InvokeTransactionV3, - ResourceBoundsMapping, + Tip, + TransactionHash, TransactionHasher, - TransactionSignature, }; +use starknet_mempool_types::mempool_types::ThinTransaction; use crate::errors::StatefulTransactionValidatorResult; +use crate::starknet_api_test_utils::get_sender_address; macro_rules! implement_ref_getters { ($(($member_name:ident, $member_type:ty));* $(;)?) => { @@ -46,15 +48,27 @@ macro_rules! implement_ref_getters { impl ExternalTransactionExt for ExternalTransaction { implement_ref_getters!( - (resource_bounds, ResourceBoundsMapping); - (signature, TransactionSignature) + (nonce, Nonce); + (tip, Tip) ); } -// TODO(Arni, 1/5/2025): Remove this trait once it is implemented in StarkNet API. +pub fn external_tx_to_thin_tx( + external_tx: &ExternalTransaction, + tx_hash: TransactionHash, +) -> ThinTransaction { + ThinTransaction { + tip: *external_tx.tip(), + nonce: *external_tx.nonce(), + sender_address: get_sender_address(external_tx), + tx_hash, + } +} + +// TODO(Mohammad): Remove this trait once it is implemented in StarkNet API. pub trait ExternalTransactionExt { - fn resource_bounds(&self) -> &ResourceBoundsMapping; - fn signature(&self) -> &TransactionSignature; + fn nonce(&self) -> &Nonce; + fn tip(&self) -> &Tip; } pub fn external_tx_to_account_tx( @@ -68,7 +82,7 @@ pub fn external_tx_to_account_tx( let declare_tx = DeclareTransaction::V3(DeclareTransactionV3 { class_hash: ClassHash::default(), /* FIXME(yael 15/4/24): call the starknet-api * function once ready */ - resource_bounds: tx.resource_bounds.clone(), + resource_bounds: tx.resource_bounds.clone().into(), tip: tx.tip, signature: tx.signature.clone(), nonce: tx.nonce, @@ -87,7 +101,7 @@ pub fn external_tx_to_account_tx( } ExternalTransaction::DeployAccount(ExternalDeployAccountTransaction::V3(tx)) => { let deploy_account_tx = DeployAccountTransaction::V3(DeployAccountTransactionV3 { - resource_bounds: tx.resource_bounds.clone(), + resource_bounds: tx.resource_bounds.clone().into(), tip: tx.tip, signature: tx.signature.clone(), nonce: tx.nonce, @@ -115,7 +129,7 @@ pub fn external_tx_to_account_tx( } ExternalTransaction::Invoke(ExternalInvokeTransaction::V3(tx)) => { let invoke_tx = InvokeTransaction::V3(InvokeTransactionV3 { - resource_bounds: tx.resource_bounds.clone(), + resource_bounds: tx.resource_bounds.clone().into(), tip: tx.tip, signature: tx.signature.clone(), nonce: tx.nonce, @@ -132,3 +146,12 @@ pub fn external_tx_to_account_tx( } } } + +// TODO(yael 9/5/54): Remove once we we transition to InternalTransaction +pub fn get_tx_hash(tx: &AccountTransaction) -> TransactionHash { + match tx { + AccountTransaction::Declare(tx) => tx.tx_hash, + AccountTransaction::DeployAccount(tx) => tx.tx_hash, + AccountTransaction::Invoke(tx) => tx.tx_hash, + } +} diff --git a/crates/gateway/tests/fixtures/declare_v3.json b/crates/gateway/tests/fixtures/declare_v3.json deleted file mode 100644 index 7fbdea94175..00000000000 --- a/crates/gateway/tests/fixtures/declare_v3.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "compiled_class_hash": "0x610e7b31000d4c7ced1196680fe0082f005e244356eb24567ee0c03d384c1ca", - "contract_class": { - "contract_class_version": "0.1.0", - "sierra_program": "H4sIABeSnWQC/8y9WZLsuq4lOJWy/K4P9s1Y6ouipDHk8IsACRCkpIjYN2+avXPMdnh4uEsUCYJoFhb+v/+l/rf+X//v/9N+mP5Dvf02flg3Pnvc/ad346cJOlwhBu9sSMEHG47lmy4GHa0z0YccanTt36v/JYT28faONdbbcVeno4ntkjH339cRettupaOKcKs7jiFY+mN0EW5j6Q/6KNdRorlivvJ5GJ9ivZ27r/b6tlGpw6joi7/SZXPxoX3ApWCqO6K+A41ojOC69GUOfaR8p+qyv41SV/sZTEzFnS7a9t5lgrlNPLy5fJuMmr1xVaU6huh8uNoAzzE8XcOZVIqXUsEZ166czWmS0eUM13GGNivl1MUe4dQmWXuWcIaklc3a6zEzof+I3lqaQ2tSOn12Ry7On9of7WnrEbOytwr1PFQ4TLzVUVLSKVzV1buUnKyK9ciRHntZsLa2d4T5HQ9iUjyOdLVRKFPUmeoZzuuwztX2DFcNOZ3uvnON2jhTdD5LdTDPTmdzKJKp3C6q7vv2td0tXLfxR1CpSVC89B21uq0q5bruJlvtEdq8wBhzrq4tsR0PbmMbRAq1Km0vk0IubVmrD6Wc9bI+Jn+k+7jzbepRbn27tobGWt1kT52uDKHuPyoLUoaHDbxStGDuvFyo2bZxHMm701h9eqeszdeVyqXDcejU/ljaqO4raR9NaTIJi+HvsWB6PH321l8lKJuya7Pv9VmO4zyOfCmjnfbhbnPWlkbltjptV9iz6mRMu8/RhLRf4yZBGgMfv+bDHk0y/XG2lW0Pc9zVF5dd1k3k3HWfZ8xNoO7Utu3tTpWybwtztIs3eYvjImOl7W2aEF+16GquI8Y7xRza0oRUvfE1tvc0iM2Vj8uo9plkj+syWrlqjRnPqsfILDzedWXlm7i3D9/+0vFu8nLnEN2lmwTqerdNetlDpfbIbbzJ5WqtagMci6XHCH3bTCCbIaOM6tDELpa2aLT749GEu1qdXUjWH+rMZzbVtKcvuWrXHqzcysa2Srpc0RXdZiAlk9oPN3aBHvds2/pIru2k67xdW4YmYqaJ+9keodywlQ57gxI4U7vK0bbw2WbMwy4otikIUkdja7XN1PSAAz0Hysrp4AL85pvUjWnXQ0fa00bQBuU+rPG6qYfzzL6Y3OYtHLd2yafzMu2xYmmr62xuY2/CfvmqPa1lu2EFJdzUcIKfIY2nIpHUsSk8c9orntVepy/3Fa9yOeXPu6mepsGb8Kj2TD7Wq5QmoKUmmIImabS2TTf7Jk9Na8GzgR6HV4EU+Bnbh4v3Od53E2R1t8k8lVVniaFN7VVvfblb26ZRnC2qaG9Ax+Zaqz5oy5BqanqDHiGf8WyjUq5pM5VjuuLRni839a2bflLxbuKoo0+pjeloc1XbswbjXJtVdUVa3fHz9qYdBU2O9KXb3WtpUpmTOUKu6mxKKjWhaSrUXm2tQ1JNvq+cD5/bJJRsSDWOn23aQlu61B4ql+rPYlw54KGOto3aBk1tA7Y7qtA0XdCnqU39NmVoS2qCOK5R5tHWnhnkBQ5ag4vo3+SmnQPp9Tsvn20rqoyy1cF540L7xTUZN75toqteoHHbo7UR17aeRp2nb8vm7Wmu87rOStpMn7UdSDlb13Rwuttp0KS7CVO5yumuJv+plqYRmv5oJ33T69WkJsShnY9t7oZ6tcWE0mTQFGt9WxpXmjaITWsW1/RtcUrVtqqlaYzaToAjmwsOl6tN3z2e1mp+/mDJLmhna41Kl3YItjsrVQ6tj1CVuXM7X1CrNTmHZzP6aKZIVldVTWH72vQaqSzSg45voJvBMm5La30F70s7iZogNEmztbQN2N7LJYPwtXPiuNvJZNoWrmeT+hxrO9XTmUI7pMOQwnmikRpvB6Gz+jib0rlUvkpuyvPM5bzs2UyGu4loUwYRDIi2NmCZuKaij3bmtwNwSI6lx6B5GlNuWbHV3La3Oo5iNWyeNiEnKNR2eJUbN1/KV2h/jO4+mgI4vW/GXLMcQiyVRk6K7QLF5psOboJGZ5Nqn9PH5bOvyVzZxLOdxqEZYUebo6ybatZtmZsmM5cFqyI3cTyONns5t+Nh3OAihVVtSq6ZTaF96D6aLKSjKchyt8mtupi2Budd24iP6kAbHTDQJoLKNWkmk7LJY2xSHVxt5zfYq+k8XTu9XIhRhWYdNtV9N3m5tAPd0pRHEx11mXo1ASRLjk62oK9LwTnUniY4bw/ftnxbrRNu096ytmnoNgHFlnhFezdDoclkO3nbFjvJRF4MYQenGRwLvh8SZAo7+9OnedM3ees6Hk7Cnz4Llllb3GYPhQQf1vZsJ4pLl7FtHU2T2ONsdog/fDmb5vQ6Fl8vMMfa9NKKkwSYNgFt4x25tkPWXc24vg5Vk8/NPDzaFjxr08b32RRo1KdumiW1qax30y7NBhqi4miBQtsg7eRvWqOtRG1qIOZ23FjV7POjHU7h9r7NTNNeoF/a6d7u04yN5EtqckEjGz+bWj3K2UzMU7UxNUvH6qudHrqZFKWZSO5ohsrlrmJ0szGOJtTt/WpPZ887ucJeyTAQvY/gPVSw/ZtubJPc7NGj2b9NrTeDu8mUN0fNqclXM++cCs0GagZTO/MuXkbaGOcJ1n8z7o520B6pjc4cpbZDstlRbXh3MyW8arszwqIolTw4KaW4SzWTgg4GslNo7lDDh7YNj6aRbvCV/E2WRnsPzKWxZP10bqtWf/tkOzBRI+D50Y7P9mn06cZ10F9rn24iB9cBM6z9fezYSKeQh6uDRR0VeX8RRmDReAO1cbcrwLfHyXLNYYLL1P7cFrd9mK2XOO9Abl/7kEMXFOyrhzsY2Ldsn6twYbrC6lXiSDXM4PgePPdN4+6eFo523GXOPM7oSd/jTzYD83WsX+OcjzzM2/F+iXL+DJyzobZPXX3U7dPtrzR/cOrju4sj1+8qXbny8yKPJaWLtotE+zex0eZl2tuhGOnv7i0M8JzYpjI8GCDt3dD+laInrwwLqHAJ6HttLO09/C4IYUDbDa4dYnsVQCzbTzBUI0xsN1zp2m/fHgJicDS/fF+P01wnGivMYruOReEbws4zkFF0hgcjP8HXq88NtV9HkwId/iq7X0Pl0OFlyLykk8A/5/NlUyjYBO2uAbYDPOv4Nm1EmPvFNOft4+em39YazdLx+aGxy2myOQ/X/KpmVWfbPLjkLJyhTdGX5l1b0wxrsDC116WYVGs7S3FQ+p5mYHpKUluoDHa7N7UdB7a009L7WpoR0jRWc2ubaX5me7mUz7Ztbn+3M8Y1F7GaJoOFA0HbQqB97XFJ4HFg6iB+sGozc398K7bXsPH8+/csxUootlOlnYua6oYleUbS9qluV/fh42+wqdsnFsPNDiEKm7dKi0a+xE9jIGP5p89YCij6dpr7Zmco5107O9u/bZu1d4xv008HHZwgMF+oVUPfcu8zBx4SKAM8OHCkPOd9LE2uPtZKg8ETU3MFTW0WS7PcdQr2bCfwnW075tuJ3LRQc0curbNqXlzIzYNtzmYzQ0jV0yVZCsUEvt2UIg6BffX2zeGtdw2LWq7vvfNjyeHo7Tu4f5rH8D5BcOi0/6X7mExz7puPp8hceYjKsCFXXe78r59maxZPFs+Hmvunb26acphlpHFoLgOfVj6izu8a7WXW6ZtD2CkgiMIN34RTzeNM8qq8zqWCzYXzD+vWTAc29+r7xg/DiX9up11raQOOpbrB2yvtjK3HEa58gl0Zqj5j81Tgjfv0zaFItnkj5+UObdqFradoxOP2+02nYI2BbVZE2w9gWcGByPYaLEWAvfQ6tefLIS0WHg5YXKAER/tyxMMhgnuXD+Ohc736TViicIDIOTVJ6VzrfR5nm8a7GS7Z6+alHEfzG/IFkT3rtG4bWqnm7LSJtLYdKP72rPSnSfY8F3e7VPvjVF6ps7lCsZlv5chtn7QvNvekKcQrNd1xXKdWEO8uzY1vjph3Z3XNsUgHOXKvFuPLzX+wd71d7dBvHawNRfHu7b8RHx9/LeNnHndIpHjGWnuKoI/PORrJ2JCeQrcUoxqbhKIVfpgAnq5H6z7GEWj9ScnyqUDKk+SDpHf8JCOMlNq4H6Wq2BAjp4NOa7L5aTXoXKOZ5aDQ+EnZGDKJaPeRd0bqZdw3knIYzxvJthj3T+P+adw/jfsmmv9xX8oHpHHfROGpcd807pvGfdO4bxr3TeO+6abI4Pg57kuxzUyRQ5KDcd/MJj0JFEnWOe5wcaiZcxZknCqyWi96h+IimpKNmhZGRw7MH5zsoBcc9qOQHS2TJTmxpEAsWTeWrFRL7oi5KdjE4eFK9vNBgeJMpgLZZ8ayNUwfJiE0JL15rAqFPSmhQU9AF81jbHkMJI/vl3GvMuaSohOFPLkxhDJuXMb4y7hvGfct4740haWS4T1+jvsW2v3jvgelgMd9j3Ff0lk0O8e47zHue4z7HpRtG/c9xn2Pcd/jWlPMddy3UvRl3JeyqdVRVGb8HPensGQd963jvpXSfOO+ddy3jvvSMpP4neO+J8nyuO857nuO+57jvpRuOcdznxTeHvc/x/0pdn6O+57jviT/17jfNe53jftRSPoa9yP5vMb9rnG/a9zvGve7KKJCe3Hc9xr3vcd9yXm6x/3vcf973P+mM4Kyj+P+FIO/x/3vcf973P8e97/H/WmD3RwiZlXAOVLa5orcX0XWkKIspwocYaYXdAKrQucPBfAs544ShfQo6qgoyExyaEngLD2JvfgFWSQ0KY7foWV2JHeO1L8jPe/oIHV0kjra5J6BFbT9PH3Gk2bzdN56ckL5BOdUtZ9nMB2KpNUDHTOBzz0+SCkq6WnRPK1OoFRBYIee4geRzuPo2Clmc5c+Q0GIQNsu0P6KioZB8xxI5BKtciShi7QLIh1WkU6lyBckjRFJ5SQ6AhINPpGpn2jwiXzLTGZHpuBxJrHJ5L9niq1kxWcq3YL0XiLZSLSTE239THs4k/LIdGbnxMcAjYeunFlDk0lX6NAsipQ2xagLnUSFnrSQ2VPoGC0kmWWeDXTIkPQWUouFBLuQgiikAQ7anifpzoN8sIMycicfH+QkHuRlnWRPHGToHWTBnTTCg87TI7HapfMk8wFDOA0Sv4PPKjqUTkp2H5WPHdLzpEkqid9BqvGgpaw0CZW0TVV0jPB5RCqlUmCt0l6ulH+rZHhUOvWr5+QCDYPMtUoLV9n+oS2j+e6R3yGUiuIPs63F1hfpVYJ3aDJ7NHkAmiOXBLTQFMPUHGAkXISmdIum+KTWjG6iYXAYkvEGhv0OGoZhZ5eGQUl5zfAJUt2a9rKmNIim2KEm8dMk/NrwMGg2yGbTJKKakrya9o6mvKmmrKzmtCwtpSbloElDakqEapJDTcutKYWpKd6lOYVIO1dThk+T36Rpy2gKaWjHpjYNg3aTJq2lKeagHQOX6O6cffIMzaG7cy6JlIwmjJ+mo0STiGry5LRnr5dmg5w5Td6cpiNJk4LVtN+1Zwt/JlTpheF0IL2g8ZC/p8nG1px0YC88cDaRxkOn3oyXMxKPs9Xk8Wly+TjZoDkeQ96eJndPc0qDtNbEL0ZGLdHdyRjQdBzryN4NDYMcP02en6ajX5N3o0kfavICdeKcA42HHEHNyW/SLZqOG03OoCZvUNMJq8ly0OQQavIINR2+mnS4zgw/o2GwW0iKWpPrM8FcmXMXdHeyiDQdSZocn5PMsPNik5lsXDIYLsMvyAymLXxRkvSi7XCRrF60ghc96ZXYmtb0gq5DDtBV2cImE51s+4sc3ous7pts2ptOkJvU8k075aZdcDPCj6TlJkm4aQ5vmqi7sMFNfyJX6ibf5yan4WbrXJjfhe1vfo8VsmK9qVirKVY+ilWD4n2veHsqdtfVwdY6S5cqfL3CZ850AS7+xsVH3M0nEYcP5hkyNfNUu1NNTk01t+uUUH3w4cJ+hT7nKz7m5ghuPkt5JkksLe0OR0rVk//iyVIKJNWRVE2iszjRHmeodL7Ztyenn3RFIVOnkP1ZyMEuZH8etE1O9i3puL9oqhiqd5E6ujILPO0Fcokv9gnpuL/psLhJjdw0sJu+dV9soszoDx+wioNHio8rxTpbsbZUlS0VFlxhtEz7g82EeerNQ24eQfPomRp5KiF98CvaS9nTAXsqlVRQJqmqms4+4NcIv2b4tf1BtV/xVTvtTXtoeKfCP4583gKhYgMfKu3bAEJR7eP9n9T+YJp/Cn/Q/T1d2z+2jj9QVNSkfh/4p2nL9gpHVuEPTZ5de6P9Af8a4A+5/aFZADB4Up8Jrm5xlLUNJcJ9Iow8OhpF+6f9yp9rvxYFaDpyiNo173aL9n77x/b72Meg5ufgqY3G9/hB4BZFLXdsH4p9PBnGk3k8OCEhjfHQJTJMzsETn2u/2QH/FBqUgQVqy7AMilOt/coV/+G74a84FpXM3T9ywPgOXib4VQhGxIu271gYiw3LjLQvnq7/FebLzvniJ7ngma+0PK6vy/TjtFwwiosnDUSEHOwhinif9n26RSZhsfe8bVOu8A9c++aDwHp8qraF+B+4kVUors7iCDQIEgioHKlck9NN+cR/cOodCIU56T2X4YoJZRZw6gFniS7Botlsa1yINjLcDH0NYGQWRmHxFYyxjaI0f4Au4dvnk3f0vLgmitdprEkznVFcPMim1/RhMpZ96SIJm6VZo+N5YU+FZuT2AZLUwrz4JBeK9ontj9+fnMWPRhAdv1L+ICEe7y2zwReGtW3/kAToWJe9DOPB95p5SmsCqiF41lIgEB4GFXiCcB6ChX8MvwcfYUTLHAXOAFws9fFk/GeVXJwV/GdKBuyl/s0AghhA0wWYkaZW2j+HLmMYML6QlmGw0tBndanP2MmzeLq+u9srFW1/VVNXtEEfNWocCw3j7LoVcOR9PBF0eWzyQ8OjVzF3kWmvEr5HmIG7arpjxS097tgsBrx2fy+254H3HH3kYAUa7nFUwGSERMMIPCBf59BAhBIOYHxjToa+6METP7juUtnEgSboqF0nj8mQwtVugXoeJr39GEoYhjcGEPlVoFcggXwJeMjk5/O1D/Vf4Q9mrMFVaYWanss0c7SqKYAkjRFEfvDIk0H3bSdI3xwJBTAazcsKj2scT8ZBk9GmZUxGKngdQ2cBjCBlqUFNGwMNo004H0nphBuNP+SxWBFOPXxlVJoCaqzLmi9/8Er0h26/mUxDMwcP9xZrMu5z8L0tDco4ujeMYnxuzE1W4kkyTUTtegdfZRoLvXfQX9v7maWHnD54QF6Uyht3vGoX844ev+yfSwwHaNdz4r7jVWa5zIbfC7yh1CqhsFPpKbPxSdxsvDoVrYmjoZig5o7Hhww1F96pcNn+K4hp5t3RbTuWZPJcY+BNOnZ2rUOJjFddqck/8L5h0Yh0VkBxG+0C0Pc09Z4mQ8+NK8+CNtd89chK4xr3cYb2jjG+TkHO0kwwAJBQ29VhL8Jluw6JqIZZhu1Dd+EATHbyucevMLK205xJYlaKITVK7jXtT1IYBysMR2oCTpQ+GWVol6vfgTIlsRs2JtOUjFdzRFWMaCgMQ+8RaLLdvPLNA93cGXr8kmh1bOQBxU2F5kPT1nSm0L4wYW5SuLelc4c0B2PhrzkbpuA/ydF2UONVk1/HG9fd/Neb4/Um8fFFEkGvMhwjzUMcSiXCWVwq2nhiHIcm6fSern8ofn5WIGTxXGpoUpBsTgFZnvZCqtzwAUO3bH6wJWVGA48MGOGjoq0X/tM3bFedeKqitN7069CfQkZRE/Dqt8fOvH0v2hp6lWDNEjwPtiMsctp/7ce6OfvHM++Z/LFTYAARzoihozQLCJ965kxkcKConFO82oFy8Ml1ZHrVBnD1ach82ub1GKYYHVz4uM2d+Ojs63Kc5lZyBta/HjrUebrSNRPdrPIWqZoEY5iwUO6peKAEj6lWL3cbv4IKjcLYqX0+TeDdMjV5f3Cra+AjtgaSz8ras7pFYfRf4zwMagFtNYQg0V6xhtclsfYMNGQL7j3bXWPmWXPk7djgP5CaB1lubh/FEMfBiQ8aWX/FVX/xH1jDH1otB2Ph1SA1NQYw/+DYAhtaq80UI7vwGa1dZG/82m5Lf6D14j/AKp3ybLVjzxfeoCWv4+E/eDEeTuTiPFRa8GYtD1k0dZGY+RFNxgcFv/iwODXdOJAOm6+SY3MDhzUUCuWRSUBBVafNDj4t71GjM8sMqX0G9Jqkp3+C0sc6o67KZP4hPEzyLhpDImFKzKKq5h9YfI/mItOTnIavHPlE491xsm1xsjUyZsh6R4ngJvtk30by+M7mh4/zpDvy4/FxzRZZoY3S7lvovo7Nv2FkGUCX0jlzslnsgvDWMI4C5sMJuuHsFpsNPC9hnRf+gy58CVzy5jigywYVmQkHUM9hoXXNV/tdcGSm/wO/svc7AnUGCtHS+HZT6T28ofqr/m18kIrjBtoJ1qDoU2KwYt4HRoY6xsYessNBobsYbY/qaR4FOKNomp03+kZw9WZv4pxWeEIHXl4fGfqFODJ8RZdAfwyvbAzYkhFH2E8kCCngK3xvxNqW4RLWrcex2jUCPTQaiVi4Nh66e+9tXuBEuOD0H+MlCEF//iCuj26TxohbNs7R2GDm4UEMvpIz2m+E5lZI8zpoDOB1sgFjJI7P1XEdfEVAPhiS8egSgS+teqAGr4OvIoSF0J3qYzQ9vqkmsgQm0/Lqow0A9kF/Rc8ZX5YdvkYx5l8/2s9d9DQNuEDj4euMSZhA04N/hxkXnwSHIMCKo2tA77m+9rSw223Bfg02OcuCiaNIB42nPzG+ZznS5AysO557+Hd8lfXyXl9Mvh/egCAA6V6kyBxPoaoazsZo223RtMQVjgqKuwndBfcEES08tcVaDdG2A54s0RyglMAlxRLQnscpg6gexhn7lKGNCKeH1rwIqEMd7EDePYQSa59qiteBZaaSu1i0QKAy3Nwd9J6u/F4ROxYtfZx8PNMzynXlya894NsetP1IBVwU/IO7munP0JtKUU6Lhv/ZfBRv+KN3X8XHNGOMkBBvvFjoEGTQbz7BgTy2KW7DCxStd2JuMQhPiSlglhifb7IAxmLNsNoX7y/0OywsueeJQXeAwcdhjhOtV1h/OK4PUunt8brUzc/hSZynAmvnvL/p4xUjqmhttT/QonZnAKw2jLf2MQvjCySIpwQDpxWWuGY4kPsjolVrlZufQ0OU5HwuXaD9ldB4KbBG+Kp/CaSm4MVZfiqHQ/OIyBs88Pp9VRryaOdWzyjLMFUuLTKKI1Csv3DNy4swnLnd4cQTH0eahYz2EdicniPI7jkC3nxmDiPXX3f9FdsdLg9fhxFcPGuUXdulrv+Ku1gqVV1JgOkjmYdxFbrolW2B4xUdDp5bfIU6Yb4HOxaWh5C5BuSXPg43hxQXvhrvJaj5BsNI4StSO3qGyiGena6b/xb7ydLemyKgScVdfGJlPPnUU5uX+jhT2sF43faAb+O5WnnMxzxh/zp5y0cwmEZS3lUGGiMwUcdlayIVgJaLASmp7qFLhfXl4Js364ibled8D2YPRlTrh+5Bkb5ZZG5vIX0AkQcDr+ZKQkLi9vNecEkyZ7tIsz7AdEYX6YvfY0PRXjyj52pwsETfh73SpeHbeDNWeTeoPBSCmwXy5nMp7jPerS9xbvYwV05qFwKhRO9TTB4O+FbqL1uIoiM4d3fd544l9D7himMH3byD7mnb49w5lR5z5xR9HPKYfQfhq3GJNOEs1nUruZ0n48ZNM43Zy8o9tlBWbJcxVLFs08cJ6F/2kNOM9GivxUy052o3CsLckQYqGvr54SGAQsgqbwoBnmNMRfOSYIPAdsgq8tNGeTw6+BubB4rOmKwO/nwZGr6/6u8loOhiZH3FlXCWFsYZOmVsUo+d6uyyIehIsGK+0CU24TcVn9UF+b5TqnPnhkVr8NU6w/fHDBMOiC7hNWncrHeN6xzLmtNwxT7BE3IUprwMlZu1f86wJlnL2tIMaytdUBe0js7Dt3F+2WCH46PPr+MTe84vgYvgevnjgVFs3LflTHlLfOBA92juN1xxPDBLA77Ky3tp1mjw8178+ZOf9+T3DofeKSgJFzfHzcVEz/smT5HGh7mUrmD5vRm3V3EVoFW8MNHdFFvS+oZvw6CMXl1hvnfPVD8drxE0ONtn16ABI5oVLVhPK6MjrthR/TlswDRHq899wscD21Jm/PMaNiBFin5tFBpISxMOU0UIWWi64qJRYCKYsKeGHdvkF49Ei4OgZ5/wQgkPnRFruKWUw7S7Mdba1Bc6XYBFmLEFfKURbZNffXqwJNFZVWxT9iMNjHR8xc/5DLSAouhurXHu/ZN0ALcBo50P4RB49CqMFszOondv+FxOw3lT/VX/XObPSZt4hiI2vYc3hjhCNgFzJV160FIEY0iEJ4Zfe8AZgEdXgSk7+KHgFTyjfA+HNu0eGVowtv52ojVFa1HgIX9ClUgG488jZEACLcMNmIKAZLJKOYr7ubRYHNPHxpiBVejKP43cfC/RFBoGOkwld//3cs1CaZ+pfQipVt9crfZNQBpAMk76+H7mMeA7ptkTEBYf7qLvPuNjjSBenk0y5MJwULPHLhWeH5CcG9dJ5HY6/3UsGSEbwH84ovxGW1Z5mv1OPn4x/IBHoLC/preGjqdHG244npBPWxxPjDKYHm+Av0aBkpo+c+nBAX4vk+NZ+NjBIEKxQsCMZhcPsWk9EDKFAxQZqnDDHobCbDMIkXgUzE68OZ4YNeihBvQlwhIrmEwa6wjM/RyBzc8RyBzXh9/pbwwvwLfxtiydGKJAK71KMQ9aWBighjCt8I+GtU67YV35wZtAaDKsZ3QCX0nD2k7tV9kpYaHCFEJ3TQ3b5hDKoPJB+DoqubhEIvB5T/PctTOKwSUBu53b8Su/29XW13MypLjxHRswXVCHz9/FWsYu5lhWq2cGIfBVd9+zdN8h8HvZt11LlyjsfGSMPAz3PW7G5IyBWCDGQ9uKqgsXd59tyTRDDmxLpos33JVwW6xRc5yjQ12HTfB1S177DFF0Y51jDiIbsstgty/+aKyLYVyXbYfO8O4Ofuoj0QQfabXWKU4h9ysEA4YQ1nmJeiua4FvtE3zwfr2fzl267XOCb/2cYMJ5/ySEp0Pvv30dJ/hk6+Um65V3G0YR6hYPusMfjHWq7uAH3v2/JYxwk/8nQxlMjccPPGPKZK2LOMKlSdFeesYbCLzfXPb2ddu9bLrxRQ883fhprs+wAZUHl2GvDPOlJ9T+YK4nqhW3DqRyBJaVotzUH+31t0gLyDSwU/7RWicWNgq9o6mMaTOg76UAVrerEYkbF3Mdhybs6hmkwbRZYiHtkYeXQw/SfJnqHJOiRNpb2gzt9Z4268b2cMXTSHfRtkcK43kkZLWlzTCZh9cB9AM9ozECy4wG86H6WJvJikjHnjbLLEv4CtNmJk+7eTeK0WBOfJSiiY1pM3N85sKIvak/tEzVfaXNcgc4jrSZE+F7x+kwx7at47SZS9LeHWmzzVPph+p2mA0burnWx0wKdohQT5vZzUUA0xldy5E3m/Y6vsK82WrDr9bCtPp/T5t1izk64CyFLCBVEM1bRlIX9hAZpovTZlFtaTOqigWTmq3ASiky9UyRYRpg2GUUPZ12tfNomrcPFciaecyahZcM2fRRZfTLv+wkSKbBrFOQKqJfANakEV6DVRxsvsikHgb62NaUy9GmvhtzeIc934YmtTdrdsVTbm2m38rDaiGLjE1qP9NQjkxqdvs6Vgj9snnUu/JhUqM17T18nRNs0+RHC+/gnMocAceNxAg4dj5HMB0CItJ5MeorJgDh62jUszcdPI1gJAWpyP6vsdRndFym/UhLx1oNHaXCoL9JwPy92fAiWRZYfaNJjhM8jfPqMJ04jHhpZJ5Lssy9nKRsnAufhY1zJbIIv0ZSE9rv9bKmL/E0zplz4dVi/kPCB9UbUTp8rMoZyYTsGcM4rVBM+Eh0jhWeDqKA+qoIs17Rqii15XvEqkxT/OJVuXhVLgsG/8hZ5O9VKWzzOE5hlpd8T1lshj+vygV36JlFHBVLNbH7/DWF+ZqxoLJNvmgFD4BM9CW/N0x0tZvoxKMt04Gcwjx4/x8OEpHDB7q+U5j3S3TnuF5MdLOZ6H9MYTbjvFnj97DhOPlnJyxmSXCdH374pw+0LUpPcAWZROQEF7sH+Gq4BG6La4rkIH/8ogSXtPTRvh8eARWQs468WUtPS1+4QOXFx9SLUYwwvN+DgegLYIZSSDnl89JMxtHj3mn3gM5NP5D1VZYcn22fI48vq4fHxylGyPsJ2GnP5rETyP6PyPDNbIUyMzv4FC40bCGF6DQ8K2ZnGAriFGcrNnzPurkjlvNGV//m/hBg6uGb/t37YSo9MiXSBj1c3B9r3tyfZ2QBdYlrntSf3R8h4x3n3h0rmSNAL6UjJf3i/SgZrR4P7RiOyKdkTz28uHzooCC0VMbuw8dEgHeRrVeFrUFLE9FemQUxip6BOyf6r2ZGTy7IwoOGK2qCIx6T/cI9pl6nVY/zMK36Xr3wTGT0Wl+88LWG8t7mAcxpd9YRgCdSxI95sDgPiQ36iBZHH25APF6deTI4L7G5C81DZXkgJB7bqjhcu+2TgU/tIXURLTcduLbAVx6jpcmAC0/juxf3vIUAYN+6a7oI1XEV7NM5ADfbHhxq76UofbQG1P0YLZHthPYFDpBXz+Igo+8dCZfIFN99A3Tmp8V9KIoKHCsG6y0qQPQbGHjuF/qKCgSOCkjUnZY5w5siAJb9tI6Uk5YcWuH9OhgBj4uAzjQaVJL3GRymNUYFptmOrzAqMExr4rqaQW2KCsz4OeLlMCpQ3ZurL3Y8GuUYFQi/4m6zvWZUYPG2LOUdRZxcU1RgggN77Bw/Z9x6GKBd/xYVQKSeuysD+yqG0zEqQJj7k7ZHPc0SFZjxdnyFUYHTbFGBF3v6L1GBdla1Iy5CeCNPEImwTU+OCgh7GmF4PSqw2NN5DYoeM/KeRlRAWJ3T83mLVGP4qEOV1BM4O9FxMywwTVHNkK3I1i6n2Q0De829Bnr651aA5IDT8lhYbaOG2XBoqCP7ezLSMwLJaKtO4B2F8KH/mJMRZPTrhwmZHrsNzL/sNJuLEb0WjGiQd9GcIYhoYHQYjEni8gRrjrQ1+qYQ0bDJvZvFeAcd1t2GEY1p2N2VIhrCPj22iAYGkO10owfOYwK6CjtW09jLFE+QtqZ7Nb2wMLF9Hb6NJh7L7n0xFExPZCdDttxjAE6rxwDAhSZjnZJm65rkZvZ181EpxmR1a9PyAObmQcG2QkJnovbavW0UhU2Nlbplp6ZUL0DMh+ue9o8w5dzvsM6+HZdAAH2EW0xtkNis0n8CgTseELgJRINoKUHgMk9y3iBwUHFC2iuzC8DgJFUZAlcnLA5QbkQM1oufJuIMMW7dB7D1BQE3PR9xwMrZC/FvCDhoiHgS18/3qm2zhwbM8hGid/5h1VhMHmnauC3sPwTbuoJ+LKxUK46zwA6alI4jr6PmILvQX0mvllmcQKi6A+o4HIqvOkDQbzndicmbBz2i7roDKvB3WUcGCO45XeefBwKOu3ug+njBB5YXfKBEB73DSaMH0T51T+m6yI8YWPokBO5LZ4Atuvr3CJWQ0seH9H+kM/AjL7j9f9EZbzkMv+AK/xbqZSK3fdUYDWfjiaA3DCuebDacHOw9maTTPpfNceDAUOKUcIQgBtitrccVqTsBm4meAwdeP6O9fqJC0poJmTN9/01puHZT6KGjmKZW7GHWCHYXE70fLcgvQIQCy0yX/aPZPVa2/rCy/3lFhnas5l3ymuKZnmHR+Ar3/3yPZIn4U4238+MUz/TsTHrjIVfS978r3/HM83riyd0znpnd+Z/EM72tZzOs4dswXM8njbfrqfLLwqJvvOx/zKGZ8D9rYTktlkF6SbF7Dvngq76wEseJC0scXcazzveeFLvnVK132VtaWG8/FzZPh3wurI/PhfXhP1rYAK2u/Ol7BNiHOWb3T7o47gtb/NfC/ld0sS//iS725zbRnvPG0FWnkAHHwYv+Kvf3mCv4qYs9b7WTIOf9Vb9I9dFtujixLmbIuU8vujj9qovt8TddDBLb1hm6VFeBxvvF5MLk3pqBiPKULf9kc227mgmVfWL1yVBIfNV3Wd7Vpwyl0IxmVp+cyfdtWwTaZSHsi7+lg7ddFp7pihzyc+1D+s188ocOhy/wbdxlHGfwhcorvCj9/tZRz42Y3s2nf99kHfY8D9k2aJZQhs75HTqXw/kDNtFzrs4ftTJ0ru4Wkef8nT/wigs2MccndC5H81yIyMZPVB/YxKbpovP1jD0x5zk35isZsv5cokBfXuhzKewv4MQflsLue+zDWdGRy6wiO/IxkoqKcSuc87KAkLmuI9dZRa6siclfpKL85VaEb/vrvNnkeCEVdT/T0P5+Qtf8VX9DX+R4+zvHE76Od7v5voRoznFhjfhQPM9ToDwL52akBEVZEGWsRwrmbJacdl7dUCnLgUPWTUaSouVKLJv4Kov35DVYlhPXbKEodOOe34Mh1bFa8GrJ5QbNNVv5eaCEyQ7AqxX0ChBKfzxRUmxWTArwdRxVZMKH9EMJ6M+rRR1hDJyQNHlsaKfMk5c3WQ9GPZ3hxPEULovJqUBIckye2cDsOZUHFlhM3gyxzMmzzyrRYN0v2P2cNSRyb/g2DCpP+ThJ0rn9mTxjMRv2chx/1ogyP34AuAuesYGRhfgKVft8j/ZDNo8zNnhCRQcOPTQPD4ts8IyVgMC+Hbz66YzN+anaJ4HH3A5L7vD1jA3hzKUNpn0bliEwJCx4KhENkxrUhPiviItHRBCkJXOpYT5DJE8hcHwEX/X5jYunQMKB0sJ2X0jkKQS2+6C1+83zez/KHgvXFJeXmuLycnYWcXa+1cS+uwqh2dwupLP0ozNsxtMYbGZ5yhvqPJdv1PlyiaILgc5z2UHngfFNIZ+FT0Ni7NKzSGiiLkp9oi4KW/iFT0PqX4DYQROObBKYbb3mOLDpEwoJlCvqoyb2m7Zpwjrmr4BfxLTaQn9Rnlj5PVfe8RjhqAPhMXEdZD4NOERHR2TIkwpI+MRrzErPWdn6gb8IWGc6MoW4N476xM+3P/ia1JYgAoRFPjQD2wcmIz7qNkdiVm1p0Im1EHhxTwOeNEwdnOJWN2MsQobjDTQwOSRZI49tnxsJuYcdAxR9irt2dTPCqhdotcWpmSDxilbD8Yw3m48UOtiP+bCM5o6dTRzn5nDZCOBD5hR6s4M981F5AtZvNEy9bre+KGJEYfQLdQS2wD+rpZRyK9yl/hILlVJnpJoMQIWKdOfncpyFu9QUfgF1f2bGD67cJdx3TK8y2j7qZxLdqgWI3AHzsxz5EvwX85OJP8kZ6w427jVzz9JdOh7PXizYSZE8JdEF05MnKMF8DyErhBHqTEp6AZ9slFQlflTuOvOG/XIF9GYMZ3UYV+06op0xYxdg5e7EyBPUHPAsPYmubdqg9e74uXJXnCkDbu+fduhEiPeqVa9ksSn1C2nfLGcPVquOHJmhwE7GtBoL+N7Aq1Dz1sRQ8Ii4F9NJstxS1zoA6h0uk4loyTNbA6aj84fNgDfQ4QXyM2s4MWrUAfb3Aj2R6eheG4pSJnmjqOdgz5/0dPTkg+LxCz4oPYKtAllOTUatccHaMV+TuCloLhlda2p7CaXEDIzFyvUxgokdxydHS2abEm6DlztZ0oyonDyCWdg5R3CKUoUJUMRyCBnQwMn72ZBBtijuNfUDjhvXIXxAEKhbzR9w3GsJZwe1aIn+n2gskx44bkRo9dnNVWJWXrL9P8G4uXqhvOYw15nDDSYnFytmPgtkqf0UaaaB7yZINpEpHR9kSsISneCZ634CaohM6Ug/LcrEcd8KSZ36otQHmZKa94JLrovCYSDEcfZFmSROmfHSdam+fFuUO1DFZidTmjBtlviJ3bmO70UxO5mSq78uisRyI27n2qH1d/yyy7dLOCjLHNB6/SgGFbDxDhFHrBGXbzKklAkQJywHAxu9GHQSTXGJEHUKI+h3vS97u7uOWtCJgGEqoDt9mOUC2YMG26qgdoXyylTTETP2hb3JPB5RQHvGI/4pKYNQb2SBHnhvJSmnFpNnjVOpXvmzsqIsrl3yj+oB1wmqBnIlMOokUKRFskVhpMXNcJEWTE2ZoSsT/hJhugbXjsyBovNMmBbJ0ySQK+ph4LsJslVrQezPtS3KWa8qfLt2zldJOEVmU18wVjVpAvQndu/ey9bSwGFyvc894DmMC7IM0X+pqxLEUYIJZI0E74v6Bc8xQCa18A06Rg46rzZnXNs/OePOe80JFB0e+JMJddHaMl8T93LlowZ5gzsAJS81AR2AwuFlHZmwiYQD+CaMa7r56KRQHYDCWyJwsDPU/4vOePokUd6d8ct9OOPTaY6Eztbq6ePrCMI9APxBLYUMsz571tTr+9c4AaxDPiTlKnIsY1OLy00y5/5kABux6EpxjrJvN/s82kCt5eNgAqWoyIx9zDcQPEGYIa+VDO2L4VYcne1us8d6azx5BQ/09LsfZEhqqc/GBEvngc6LUnYPFxYMnTpJlfuFzq+C9p2/meJFk8wYT2TcT+xv9+ptuccyQ9cZQfZKARUYu06VGohdn5zO+Ir6NjJZlGBAjuRvTvajHjy4t+ptQRmd566NBF238XcH/eK27HWpC7kIkj7f6y0U8krWFBZAcdphf2/QdfTfw1056FCp7Bvinu0NC53UCLqe7x4Jo4L2eynv6AXt9yMl08miTP437Do1MthK4CaEBymnOhE0LxfWl5PbLSh9J9344F+qMuMwiKBZBIsit3syJBwroxW+Qk1BRNCDcbkNqhm0naEZ1MIlgtXrOdFb073EsSBkm6GBC1euiGQIUi0tDMUIAESrC7192u8HYcBnnEBM6Bz/JF3C6ABiwGecAKMD3elOj7RO99pVkpRMPRbR+YGVdG+7yzvd+TmC6c6X/FHVXuEOl4Ovd7efDvrIJqtw52kEgkCJR7C78+kXm5WLrsHj7+xMXJMyc6h2L4+sn2yO9RMdLSq5ySdgi6fdoXL9tCCLvnZ09IwGYOICJ3hGAzAG0LE1swZEQySBstLdPJiFJpaTP0q9FHeEhT39zef6I7gGHHyoEZc0RJF4oAQX06Oq3T4Yc+sjKsA8UF+Y9Suw441EUlGsyoPHWVSSF3dRjlUSOz0x6zOpwmJ/1cVj7VXtfAJfxSYmc3sncu5yPyu3M7E5CO8+pnejGLOlf1iVZo0f9br2sABVXdePMMLLqszy8wetMGvVQSGNUQRRv8NlyBxZ4L1eObQjirS56pojEE0l3x0sOC3JScd0MzHFnV6Krln4EL6cnWCd/nzcj6rry90JuZ4W1mmGdeByDdbpl0dMC8PPK+v0SYXdPVByLsxQ/RGnpRpF/SVWt83VUjvkIKSfWKeXSu7KReN1D5RcP2CdJlm0A6+fi8b3QIkki673tWOdmCtaVo27l6pxy06s+cA6ISszEkGPMAKT6ypOYK6tIt552J7z+wOnr8BeIEKdiiK4AAdfdaf0qyhncUAtF0VwQADMt4Mn+LUmZ7rjzzjN9XBKRVHOdEqZ7LkXs6q0APnRKQVW5ubg/1CTQ9b9bPLzl9Y+m1vK5bmUHeUK5b+7pS+JMtgTUVX1Z7/0wRum6J5/90uF/zjyZ8Mk/7tbKoFKfuaJ12p68Eur+/RLhavyXrMOpktsNuy3Xzqpx8BfzDUwN1enzO1MAwU7eVXBuasmvdhK+aUWyi9PidtJ76U6S80LgVAUXXN6Ath9EC7XEWsStf8MMpIUuD1vK4gzd8JlT4TLbWJyfG/T03ZRjZy27RlmTNuqSUmI8OIqOv1A1lbQ+kamOZuIHWQfiKuXCbL0zORizhcJl2P9jXA5zjbKvfbZL74a+sbzvW4YuiV5XbVoOLRlbe1H1hYSwblmzjRHO6OrMZrePYeyts2v6jGUnrXNaxMVt7wnlLnptsq/0C3rWYHZPcqOsFhbufSsIwdliZgA6ZaXVkHCdwgLoQCmkpFueTL+TtulSF7OkQxuQ2hTBKlNwfDlyMSaDF/CXQ5PXoAL9cPBxGER3dWeGXbkBVOl8iFTwci5S17qrchL/epQgXd4rVTeyAR6Hx23EH4tlcrqrVJ5knBhUKB7qez99Eambk0Xp/dK5eZIoZemRxMb0eYncGb42nOwopHPHMBs5DMHoEXuUr2nyd77+FCFKg5guofIRPFeqfzwibt4/FypPMaW2PpLO2mqUn9Lk2V3BU6ThQcLL9v5EOBi58vuTX6wLqrbJnkp6+/OV1ryut35ikuarNSRC+6+l2jyw75X+kqT/Tx5xf/sfNFsgBs38r7ph7zvAwvPl7jVI+/7wXCImeVOxXy4qy4cMfcj7WtEevhgGrLpnVa1O2zq6c0ISi+mq+ZUcPMDj2t32CYvrmVvZsIHKjts/tVhW8ztw/2NrRp8Ncw9/1Qb9xKC2INKk/pCMPMCWQlNnsj+MkHE5Xb/idtQsbZE53FwjPGENteiMMfYDzWP90sbqumjjdlbOCFG+OJvWVil7huJvbp1L/y2H7OwT4k2exZ2hC8WP6e7iiMLaxbnLi/vcRZWiSzsTNt6zsLOrkPWac7C6kcW1j5JEmaCciZt2bmfjuTcDr9nYZHGwLd76sGEUZZM85qFrY94k/DzuCDiKcH/x22CvgJOuvwHbYK0Vvua2Q8F9hbDoGVxmwLrFf49XargisPFnu6n2xSYoB9w3MjKz4/THnTsfszGRKzAZJbWEeRcP0ObIk0cZZOMU4vH/SPP3+xLJAkOXpsAIVrLfaG1RP+nH5oA8QQfs+GQY7SWC1tGBqnqtiZAnWUbJ5hh1kiX0Cc4zkKb7y5AcF9mhzHqZYbvd6LALYmtZawy2C2E8UpG3nPPbd0E1d9vRIE4NwTbjmAbj4Y+T070jYx8a7QhWfeenOP1nLDx3lq4uNFamNL8k+DvAeM+88+dhbkJC7s6E64RFjC5cG/Bko2WoiVEawLfHGjyCe+AxBagyfPatcfSEy8dejrNd2xWBLdYTfUDaI+U6M2jogjKCFSDb5+Be5AcHAwjIJqcmgrPmMQjgiKbN81YQu/ee64A88O9N4ESXW1GE6jzownUFkqYfIQSqJ4p/iDw5WmPSQTuJoVNeKYVuAUlUBX1dHHv0tu7QLHEGjrczAIQ1yvPNbKsraGG3gXqJdSw5B0TByXS70EJl0YXKHYdtcCBdyx5XAIVGJSor5jzncXDfAUlgJXztEyTHnuAwcyeb5A2Huqhad4eEutRiQ1ZjVEJrZcuUFwJITsP/y0sgYlpfZ5OesETXz4aEql+3rJjCncZYYn7tW9cb4+z9s5TKx8iepwYqjjUnoOYLYuROTwAaArjFVqBnycIyjtA5FrCF5sfDLjvfHpOQMfOtLW2dsZXHbV+uDI2bNOFk/NSUaulSaK4e035i0VtIz/E2MQerxCxid5P+BGbmG2i3JM0fD6AFkUW6T028QNnOGPG9UzDE3JsRqvmAGa8ggcw4xW9b/NLbAJR2yHDtxkwPlPSfQB5wQFMCG8WVp1Wj8ZD9QfQ+hZYeO0Q5D87BL3EJv61QxB8bmiNmUh2x7NDEIb73jsEyQbE/94gaI9N/Dx5Jf0xNlE5NlF/jk1c0g92MzZhHg1+1VdsInFsAjPpMzYhuguP2ISAruMVKTaRlGxLtMQm3NKG0G2JY3av2RYEqke9xybS0sK4e9eTtv5k73p2HA1fsYm35iOvyWS1MEB+xyZ+3kCC2Vh2ErIXt4CaVTGJd1B6xCYyxyac9PP75Il4BULUR2zi+Kbteev9NeMVhqN7M16BAMU/xyY6p+R9jdjESw/jcb0ldJDNm+v8NsHESffCMC4c5xlMyBwdUBvLg/uj59xT1U6N8EBYCr6VcCilI/lEuD82LZ3ykaMraTYX5uhKemDc6wtzhjo4ujK7FmfIOBPGfffVBRY+PZgzRDCC/Q4RjGC/Y6a+v5kzRiYavo53uxdKyS6TT+aM7iLKuMOfWk5TfIOpLLTe58+pnyIdWVwEww5j/txOx8BNkdstHFNxTSz9w2+bnIhi/gROneEEGzGB1nlEGzohA+PUe0vlHu144eLS8ZHneqAj+JF/3tWSn1ig2Qmx/8V4JTcFPcvJmP3zwT44ofPA50iY/RnyOGSPRITsP4siJlthD1V0yP7su0zFlyPA0cl22A61jHt0L5RXLqmnYbx6qIjHiHbDY0zQfjdvYGcu/X+hIh2chyB7Hv+KxpjN7ZGs8UrdWXsAL7ZuBxKIT5d4QRrg9opewDAw5nll6mkwwivcSu8NFlHSn5oaCEjf7Iqm00fVAGIompPilmL9YQy6DSUCimRUDZT67IwwsYzn3HFnnl2GMe3zVjeAzQ/QuyQenxFhYdAUtUBwBNBorw4GEtuvgv3Zc010NgtUOJCnpXN+FOz3EEsR5PkLSj7srGod8N/7m214jaUT3gxDhL0TXi+pj1hSb2a7i3PrhBch+teX7EJs/OiEV0XrtFGuP8ELNu8A64540AtKvhcO3L90wivMeV9+57ynX7FcX8TiesxBLa3ZeieHvHRyQOzu+Nze3eGtatVQwXuMjjlUaw+nHDOcIjrhNf07Ymxm7aLsuBOe+y91whtdkGNMI8Sy90NGHAduYdmoDfvBjXL9upfrX89+yB0lnWQ/ZAyYYN2A6Ie8OV8AZ5hdjz1AC69jafaA+7FX4YcF/0WA4g6ViLFe3LwEYR5YoTA76uEr1EoI+RgBFimhpoupbOWww6LdW4mCXBMEQ4DUPGrnlxKFm/KhI8BCpfRrlbpbgPPzAfaubD3AIg8mCG539AUujKisT5y3mv1milvWZBvBRMvPEUy0/FE3B4ANbDXgF53EaiY7HXdYDc8RLN3S2EN+0uL9VqEwDhVuW3D5vQw/1R+g2iBvhgryoUqeyvB3rPajTUT3EB9qY1Z8lyRL87t7XVZy/rKU61MpPAEwFGf3J7i+1+G/dMgaMRaxsI/p6+U9fzUme/3B/XP9AU7fm4M97S+sP7i/EDDY3aLXH/Tey7M/25MgbwIyML/BleITlWG4U8VjOtipFZ0tEE7RK8WthOIzG0Pdmp1hfKTL9Ckax5FTNjkT2akV/dJm3d1f6g8SQTCUbGb8Ahi46hZmWsk7z70vM6IkuKRmojIuzXtod8kmIEM8yc0KQXTJKADGGJN3PeodfiLIE6D5OXmzFGKGqO5fCfKUJgRGd2gn8IQJ8pR6gO87XOlR1v/D9MqgjX+Bi7tH0EaU//9T0ObHNm6iH9UGBHjwJz727IixMI5jAuW5APxBQK7iT8UMjOkEzAYTkKvywNpPWD9FWGRTefWs/+ZeF9PxkL0ujq9iBug9gdiKAe3gAVouALcvxQyPiNELgeL9G3Gre7B860+uUNUDpm/tQ/RO8y1oAqB2n33xCQPQWymWAFfM6dN+AWZ0X5y5Qkd8hSIDXXwmUAJjI6OA/gUa4+vTcXshKJBP/EVQ0OMrNI7jAd34yFI9dwSXkLu4E9WyXw4Ij5AYucHSEuoGjYlPSomJ5kAMx0BuTDQHhVcmNMaZpXdDB248O4wKMMeIrvAl/rUwxDN4RJ6Oir1+9WthyGApQDj+EiUBMWjuB0N7Km7eZ+RhbQpJPTgRmYFt6gCZwSUrF5esfPeEZNRBJZgBx5L7rjVf4ZNEAZnN4VpgBug6Ijogwy6msn/DZf/S2sgbc4s0OTPBDDA2wSEXJiKgYkF9WcIZ9JjFwBlMU945whmUFbcuW5KiZzu9ukI4gxlo+UQP0Loe7lecQR4713S3kRtASmIAu+QREGcwP4rxl/45t8ROkn/tQtxJD9v05cszWULstTCQmRO1ME5yo1smrRPAFAIaLIUvkkJAzca7f8EZjFaT5xUkJqUSJkW0kl9KVQCm0nEGM9YyCATUZn0V27kJDDQslnGS2TSu7r3kZzhmRnsgWpGvyKGP2Gte+gRV2VeuowVifeHjXc8w1GbwGVl8MsIsh3th0JbQi7T19kBXQsBFhgNzESpEtrLjWusRBVngG/le4BsYcMFAA8U3yspbtwAUHFHDYXUHN88sFEPgrTJjH3POJrykIzHyQl/YveHsJM3LjH14Ls4NLwUuE12CYKa84LTFAMJa6PNCqzdRJ4JucCBSuHzDvPS8rtsIsDhmcekfTPcf0Bxor4k8B6Inc2B3JOjdpddfGaKtyVfFMAj1rd/dEQlVsZoSRDOcQ1p09q13S9/6fuLbtYFmWaAq1ALTug5EUdx7crIYdAjIywSHR37oQdjdcXV/dekxTkORX6UGjuGN3uCtj8SPkRabfoi0YPxHzcTdfyXSIhvXP2nU/+uRlpC/Iy2/8aj/KW1n9wYZgjthb1RTdp/+5g4Z96PVuOhw30Ekbm0rSiQHonO9ftpqEl7Cfv/kiLyXnqDdzzwXesMRNXmh0qh1j6H9yqP+FTTxjG3hhz65Lut8VOWHHxxZAf641J15fvODvnB2uK932B1ZQVU45/d4zq9EoHw4slhRgjQHg78wTWgJ8xe696r83rr+/mRu+0NV/qQhcJNU8L4ZGzG58RRjI9Rjq0waAre0E+3QiElNoCG0MVL7O5+Rm20q9Evjyol0ZEJywTs4XKQXTPr6K0Z10mBCGMiIJDADRp1Lo5I3F6O9B1GE84JUqJE104/qakYyw6MwkhmdsbS6ZVY2hdc7krkX1nQYPdbA1Kky8EL6JQiXGKY+ofcTyBwXlo6eVRQJAOFqfboIhdjI2sU0ZxWNevTcjiVNZ2ImoLurZaerRdEtcmLnR7szZdGZWhwxTCqaH3pu6+swk14eg5B4PuY1qquW9wynFT8IyOzfgu7o0E2e9/OqTTEC3L8OL7BL0MgjTnK63hFI73UASE7X3Qm7uRNGMjpuecWevR9FSAjwR4U1WdgRXHXdC9eb4cj6pJcjDCuyzGeQsFH6jq4OKJ9qVx+oo/8wiRg34mlvlZMJa3QBFqz8QqkdxuegbiCPLdtdALMkpO3xRMDnIOum3doythmfhbAf3e2qElCWycpprwrjKQYzm8hMoxqVlF3Zrfhst2UREZpaPc0orcoEwCMMAVdlEqxnzuFN5jh3EuqyGWzto4kAzjNPhWp1IuCxAqcn7DaG9dFs/NgBxeWTAGzoRMcl4AdbLcdutSyOU+FsxezRUZhjZTz5dJwwsNDzpxudW1ph+eSSY3Z7o3ObDy6424+Fzm3tmvY7bBVcpnYxxPl3blZpSVZGO0osPnojaKZPv2QObVaFMtm8RLfnsOSQyUzXP1Jgi+r9DM7NMNPtXgs989WdE36lwJ7BX7zbUSVWfp43orbfrV3IcYImFHYW8k+wPDqq3VPKK2DA7VmV+mnfrCcVY/VPclUEmH26JX9NNo9rCDA7X+PPLpA0iru7I4rveW7LYvNsLtDu7lDc/enurNi07u4c0l74jwF17X5vgLo2iv9pgLoj/Q8F1DU1938NUHer/yGAOg6K4zl3m4WAFoPCr1RVAKi79RoD+4r7gwkR6+S6rXh8Hohz80uTerLMDYHhZkVhR+3lpWNNh/8VbsK0WhJ4Ickq260QvNACiwtq0k9j1J+VCr5Co3zi1TquLSyfE7WHPe7Xqwvz74Z6Jfgf2jA/MQzHU4T9JREtmd9Onm69umbjrA380bDEZtFSdw+K4Tzsm3x7bRaKWLTThVVlCdY331vjzYmqC+WxJs32jtl5g66/W++IM9PnDfFiiDR3ddYstAX9dy2jwAY1w0r3frfSpWElrfRhOrHeObi48HIvYeJjpb1xaGxxWap61BbqaVYzF4AoA2T0H1dAKiI5ojpGDKYi19EwdzSGhGQcF7cesV29MQGD0ZLvyKZKxJLHXshoF9ogAahDrBOi4QhGZIgK2JqP6n5RNyh4YuMSkUXboxcyxqXjjMwTLCs7CUnD7GpD57eoLbR8VgsOf/tBBTx4YuHrvWsMj4VtI0K1yBFMRCCPYJbriRFs8dVXplroDnNF+DrbDdMi6hZEerZY/R3Y16m8a33utosRXQsbrV7YaH8D9h3wy7C1DrXbWpN5Fi9JpV9biW3P1eDscUAVXac+exy87WgwDDdv9NPI8dp5gzrObAZguXDuJQtwP2JouHtumaMy/xZuflYq/gghk8v1zvOJIdxeqIh7fZxvvSLwWCB3C59kx7eNGWfwHL4aIdu0ERkPiqOtuwwTATkRik0csk17yHai3gV0TfOMHy/FdmXDbSFMbo3Rvvl80G3GXvUe9ar3UlAoeD5/2yB+hx5Z0cttPMq9o/buXzfIhG4yHG7HxUwfrCPqxmq90N7SJbR6gPZmaR8Gczsu5oX2duJiGGTWg8IdZBdeQHb+v1ZXOllvf60r/YK97nWlS5Obf+C07Ti79Ghy8++cthKj+COl7ZT1ib37J4zij5S2e2HprPr7KCx9mWEqLHUMO/MvsDP3hJFOsNy/FZY2Cw2a0zg36vr8/1Fh6bMlu+YmtIyYyozXkARJozDSv7Vk19yElsv8MtjWhGFeiI060k30zHkUlgpg3CyMnFmBWRgZfm3Jns7bxaxP+DoD3AbCjusi/9uFpYaTT+ZBoRV/pNA63LyI9px98movzJ1cS0bBNceeftQe8vx5/SzM9awlAcLU97TfCqAhVNqmL/iRf3K8cI5ptNwLjZYLfywsPdy/ZKi92Zo9ZJf/XljqKukoVzcd5Rkz7A1es28KjltnOhi4sDSd17PbFsfDM1Y64Z5g7trsCFrnlbf17OXvnhEdnrtteffUKn4Lfsd4qQ4o+4Xn6plT9JoUSHihufL1AWXY4+dAB77lzSBMle+sArtLanBigHfeYZaZBBCzi0SrEQVuRm0JQKJsEl2fzyXRyZBvzemZRI2EZweoPdMZmbMJCLsJ/Ins2E/gZFoJrxbOJkZl0oS+FwnehaGUnX67B1VmQ7jZ60aj6TcKCgWw0nGZoFvBlrHK3tE/wC6RtOnPsEs8E7GiMK8EDB0kaZbkn9m6Vd0rPM/uxSCvYEqILzZDjlyWKLJ3KsYbUniVhL3tiDYCw2DKFW3ZwZSuboR3o6Iwp38ibXovFZmpv964GknXJggcsn4UV7GceEsV84gZKspvt+ImS294KnGTPaDXex9Rohtc6XjPBswVS2AP9Wys1bOhPdm5lx86Lj90X+WHeIefyw//JXGI6hpOGOruci3Nt3sy8Xg+gEwcUljELWF8jHb1XKJlsOSWS7QrIftWfjhHMBOHcwTeLJxlL+WHWEzac4RoG1clnfM+gplL5BGYaaP79BPM6qp/qT8UlFw9FjaSRjMBGfwLV1gPZMhQ2sSgMVXpDOsdyAk+4IwLy8MpqpYlPJK3jI4LoLcHMmbpk5/FmL+jrGaATzHP1ygRJRE13LjXfDfufcwvIhP2LCRlLe1XkUciW7C9qmEhSdr5T5cAXk9u6qVCtmchJUnSDOAx7+QM4GHYroceJjlYrufGfyryhGHm2tba3l5aqTaw2NOZ7fieP2SbkYjrrbFUDjuFl1tigrtxfK6I7yuBXTGiaeXRrVrQi8MlCby5HwcCvPnkP53BN4zxDfBmkkwFWC3ae0VzCO3kvtV9gs+XKlkWjS8+8D/wyxKP4T8Rgi8kX9wf4Z8IwRcUJkd9XhjBRfDw2RNIxOLIjvxL1dAkBB/gztUgdms7mTfSC6oauuvDct5c6m7Sx72j6clG6mRVN9TrRPJgLAoGr4PZW9kZtV8nKGmjzv2q13IjfIUmqt7opcv6ua1Xzqs9WpAcsnfN6aS3va/JTtQEn9TCGhUdVZncYiLvmEIUXzEoRTFeTUm8WqcQ1T+V9ggOi46oO1Zr/A1lx6U9Sz3UtD7D31C8QFhbBsaOgwsEXG/Wp1tKedaSqGF9vnCGrrg3NEbND41NRl4aMHAX85yblzrHWSrTDX2/N+MxPcknG89mkrHsezOdkTqzsdcMGXevrVM7N4aSSYDy0UQTDKqinJm+oCcOUEHGv3YpRBOxWcYX88tEbk8Sn7UWwmCZRnTlJpqi+w1TgG5cMJRaHgaaMESohaVIVlcmVp4sJPNJZp7YfjXRxFqkgoVObBUPDB8h+8xzBDN3PEcwjW0cgaChfj9MJDVLr2TiaqWZ7iaqqQcrS69HsVublLQxtSDqjpla9uqisvbeccobOky8+W6Q4F/6AU6QXmbOkJm37h2O/xYrFjQwlvKls0pKGKKIlaNsbqaKFYHIexiVqYqC9kn30ivgdubNn3vvSJKU23IfBLv3QRB9XLGMiayAHbY+jR5RizQrVtY+O7hwouPOP/fZOdiqrMxqm/8rRo/sqoodVIcRnSTxSzd6FgN8MXoET0xgoycIwzpwyUXID+LlYeG9WDzXi8UzTW22eNj2wGyjURITOUJczWi9Bp9tp7p8K3+MnQJ14cfPvWXLM7qD5xo0TOb2318mQpgHv6DiTyPbaQSbbDeL5rsihXI9TSVIOxpt1ONJKs4WhspmpYHo0rd29UPgQ7SP4o7SaUG4yIBqo+6XUx6yEKU9v99dA4w23knGImcTedEHAC3kuxP5c4LpizPsrYKIGgQMUaBYZJjGUje6YCzTWBJjWU0ySIQKYvDKApKV2gLva00k19iNSOoa9KLjSIVFRFGPSOwkrgxwncDKcHsFDXUDONkIuKRuC1cvsTeof4UFC/X6GYcmWsPnbkctZcaQd31ttlAQv7lEERMjj5aCjvaN6DCWnJVAS9qe/N2MyGG3+iSjqCjITi2x2l5z6pZYLW22PPssYtAWOFd3UGOvuigua9UUfd47Lq4pehSobGEt4uSQceZ54omcsKg0h64Os1AjzPgXNpvokyP8xkyXmIZhYkLtGVnNKHPngpzMoOOzxxzTljzAcgwbexKCERTd1E1rZDWnJYAHTVJXsg0gUlmrypZqVVWf9DnjJrE+biLq45lVS5ijMst+bWeZedGrPfh7VRyWsEeraFlBrdKd9KucPBhhv2UJ0cXnnhHi4iECmyliOU3jS42WjbIu304TeqzssYKKhxZbo2gUXZREPKh5Z0nE8u1RezawPfOEcJhkTUsaipxguLpzY+5i/7YYRaJvS7LjlfCP8j4zNDAiAA5NINFhBuhtEMKcaB9JZ2mGo5MEdUM6XnV2oMkT1Ikp3SP9SfjuEQjIyWwqsbZLnsMkqAhmFH1/hOrjVduYbXvf2iqB3FnHBTIFJxck49/O++PN0VeddkY9+tZmnFfROVc/R9VbHQmlK06ViCcc4HRkYAD2GGT1RWoMx+ICjUq6nxrlAlL70t2Gvev08xlR3YJe24563ta2PmpDRLYMtDsAE6QHmwldnvHbzCPCJWm51xieSwIx9wTaq3zNSrk04AqrUuk1Za8MFujwO8wrzmSDmiGCpPYnlqwiWlBs9b0wdgpvVcAW646F7/V0tUMqMpMi+aflhptB0PkMYllpEWJ1qnaLUZyUdRh1w8lxGFSS3YhoeU71wu/UZTatmeT3ZCcbIXABSEtLS/LuPa1kvSeacLh++34UPaMZr7DINu49/1J5AHvvmntP9HPCfebsarS456jm3hOjKkJENdhE9ywsxd3lMQBnljPP9/2Ynl6GybjZPEZpy0fUDu/gdfoUUTWjU+mxmlu62hvagOJJLAazzGiyKnYLLJbLK32PoWlZIl+zn7coFdWI8Pj7ZsOt481CU9OBB32zrRvQU9MpwqEcy/pNz1H4DmEaVe0WGKTrz/xSlY9S1twqFpOKwusqORdNDcke5WwVVyqK7biHa0FAgF2XEfEqYq6+Q1skahSW0jsnF7ULQl9UtbxnOy5h2LcSeNqVCtW3qB5Mk4uEjcq9mWIgsto9fWjvnbUaTT/JWt2DHZc4HrIgWUHWJG+XuKflhNXWZBy9TPsgSEFZ8m5a7RSYwmvMcp2edRbvPZS5UtsRNN9bgpBKqaV4e8NVgMuvZ8+wDMMLy05B6IXHACyHmZ8B0w78uJcW4lgmY4/N2nu0HNv1BKKdfH3ThdqIzTYafy2x3N4gbPZLstwgbI0Dc5vBF48bUA9FWw7wdgKqTtyVF2ZuKaG6B0vrxDXYvFQT9Xk0sh57RhZ63FZKaCfDRoapAsHPKKwPKaEctXqLvnbkw5DaiapwHAdxxyu33MtpCMHfoh1JCtQQdUlx90bbpUzyTpRenUvzqL4aK58VCoo3W/HRSBhp/yEoCObYBGVHTPhEt5zv4YB6qde5FCklK4uP6KLBf0RmIIBZ9AwOx17AMjvJ2bw1JOuqDHmkhqBg+RN+buUZz9STvQuFKIS61SYoPXxcMcw7+7fLCJz3S1U1vjLre00moL67C8oETPhbAiYwK7dUKr0JCtQHFXQtUFCIDlz7t4ijSUFU6YmA8cUk/dcioY4YLkQndi4g1F8FhBjPFsHEkxnxRayYmw6eaxUVisqpX9RwmwkvHSYMBL+JCtSEF2y5OtUw1DqTGGAAGMXgKra4K83YbpvBk/qyZV9kEdUiBhdH6a4Ty4lEtbXUF71gaTwIwBL6ml9rtU/HYNxLmyxUGOJzhyDcwgjibJl2Fea5ntUZ6vnexfJVGIcwK+4zlQTL9m1gT2APiAffFnEsofpmTOWtSFGJcjA+XZdysMmxfXOPiEk1hdAG1FQS5KDiR0MEKX7Y2uzrSBOu9RhGWOi/UfzE0Ljn5cROIGIC21fc7u2UhwKj0k53wkTEXsFk6rOqSU24qr2Q6mmUSmWS0HupqssyxYs3EDxqq4QidhUlFEuOSELNfqJ1nu+6cI+Jah4kye5COzuM3ZUEdHZEv4sI9NQXFm480U4WFD7RZqHSTWZCipP2R7JAEeZV9jXXQ06I15vKQjQBcpdG5XJk2Ij9TU5Ee2cV+JZhqUAybh2a58/5zQrttUsvegp7hRV9M2tVRK4qFBS3N7DEhmB4orlmbRMdbu8KZrPsD4Z5sOwlS5/oD9YJuYWgZE44ZHU6u8BrUFC46mgSZZPpI9/DOiEUlFlF1Dm6bxm0Xymw3wQFnMJiCBVzqi4obxutnWdpmj6TFbv3+sLVkF2/JB57a+TVBcV/CQqbPqJSBw+0yaLdXyW7vseSIsbGbQpB4fo4t2Iv8XkTFEyOmcmhTTmhTkKt1EpMTUD9SYSNRT+YxWyvrihAkVGwU6uFnRoGJbi4HZMfOIw8zEto0Wz8HiioWW3T+4Yt7AeM+Hexni8NyIeqNsR5fAJJEPVWnnU1+ArZD+gSoNT3QhpY7FlIg9U7uNhLHQ91GTOeTTTPwCivf+U8TlmJmjZRhdNfoVSI9xxJhWPSu0F+INhdWCrcR64SS2OK8Z7ChbgubIUihAVyvt4umcJebmS2FtqzuiZdJF0e58l6R1QX2d02Qhii3fSC79yzmoYCjOdqQAKHgGfbEWtpfHomY7DwJuXMmJeTMcyo87wHn5jMN3L3wLUfA/B2nEj9VX/PCIt81XmeAhLtQPUckPB+Oxwzc0L5kH0m/ea5EsmT5SXfSzyUvHPl4KAhCGDIYj/BE8GDMLNP0l9l8R4Z9XwCnyebz1zS5E86Cv35jyaT/yEKYNJuMml2WTKOCGWZxwbjHjIfWAt4avmVyclZZRndGNPmjjKyPlEUwDPTVX9PvXp8nj2+JiqBPb6LDSn6XEovRpNnLlift+BVDkQ+nUP0mcmn1ebxeeYfwFdmf883bTzkhxEs7Yp0iUORujl+DpTncMBUkcV9hvKzx9cjVf2EDHONTl6jc9mwSnFDep6Sgzw+f3x4fL4sHl9z6eiAjCwCkcVivhdYfILUG3GivZftiNH1N1UYwQsx1dct8OZPyv76M0fWIHjQLlJRK39OM14i8jrF5NvP6LH4jxle/Ul6xTP+2Z8UMXg/2DyQs6aDCDA9ZNi7/HCuHV/JqoN+sDFZgL9pjTwD1vxFVWX+qs+DDXdevN7RMDmWrhLfxOdIayFbmGJxE9WlHBqfuexD+Dt98uLBjECUrc3ImUipnImlh9NV7RVZG462Y3KQZeiyEhR53kEteUIvuifmJCga2tfJiRlPHTQJS2h/ncKSNmGR1+C+v4mLcVN02Q1ZCZwPDxxydDOWqVP5ualhgPAPEBF1aQGt06UFXy0MaqpYgQ8PHOsPhpYk8KEf0JkQxUaJFUM6SVGn80NcUt7EhYK77A3gq35P8Z4a4iLGZlcnWEv3Njj13nIxoLFQc2ZjAWP61CxjVRmYl0JxaTaWo5M+OFItYWa/pg7TCL7BM5wxMzk/5MWzvHgwXZhbly5C4f2cWZFkioXI91LwpFxCeILxcqbwfn/1k7yAyZ+YtYvPp9eDHYERJC9c5x8oV2bCPgzKPGTOPOR8kbjkL+1C1AT9XA+JoraBLfn+nlrfi6Rd8BWRqE1g2L3typDch7jAiZ9OX/SqkTMmM1A0SgjZcQzPbadOYREqFO8JnPUJRRfKVGVOP7QrkgAx7qd9WzjDbyd7adqg2EAne2H54QB+f5VlwzM41zmynkulNSrcwKiw+JRjh84Oz7d82ByhfIfSqJYWggv9nsXO56eovxzbxWNjdWOI1MR+BHMLZLRKMwXoeIwQaOgSlK7nZin27D6Twlepv1dnxjzdH8+6QlGoMjTdC9015o8TM+l4xmu7mV4y9vh4FmjchrztHfzrJnNEHDnzJiPjIlnwaOdmcLc/mZ/42m18axVWnN9Q/Zkd5Nyk3AzwWHHkNvVXslVYEUQTVA5V2J0t4Lrmfq/A77k8oavgs8M8ZT5/oLtWT9lns8CbcMrzzHnEWeVXf0AE5GK+ZvzIpri8uj44ndmEj+nsGeK6FVnSs1z7fHJdSLbaBJ7PynNBbXjaKw4zPCeUXd+Cyb4+oTe/d2Ym3x0PkzlclR0hIPKku5nTOU84luDFBXvMZv4ws4tvtssFpByOYKNI9n35rSvegrd8n05JpO1ZbvAVzpUXqwN+n8ne/cRTbHzmi0S4Qp+rHLbqjOLjvBlccyEqzivfBc5V5tDa3O2ZQ2sZQmveLFxoqMybRovtXIdv4w7n/QWRgr7Xp5uuF5L017n63OxhbU29dwNYNg4CGPLOpbOAcm1dKb1z3PWQv75aFuYpDHRQ6lG2UZa2Pbhx+Kxvnom/aONw9UyhSAHi0VERBffcN0xoXZoJMfZNMPweqdN2wmcwKVERcdIrJ1ZE+UURbUmvDu76URHFL0UU4l7++B/sHE7f7Z98tpxBA2VZ6STyPCazAZMLeZtZYMn6Sof0Q0eUzIGKZriEg9d5pxzLHAQBOyttHVFKWOtq+zqfj3XOZq6p5ACSRkmuJtz5gK/jMtcZKk60zMeiIN/Y9v6wysHubHtG1BIv/Xb/ssqyLkLmMf9lmWlRatrWMOqvXsv5VbpwEePeYFTPRax4yb5ZIy9KNLvVEMNzs0bPn3e0WSNjn0LJlQFxJnaAWOa4SD4dreKlnpv1rD9bDX9QpbRZY/NmTIz/TVUq73vtn3x2RnuqUoqg03HDF0sPsdmfk2InQNK8HjjwrPW5/+LxsnSFMwsfjla+SzzzbWJnf8lsJeeLtt9/tCodrbCFsf+HrIqJX02UkFJDUnX8sC6FaTaLwiv23cXRr/4qL++RXTSrr0ty/HlLu4thrCX1Yiw0i8rKYidW1hT9NCGLfpqQRabF9jry/FVgV1Iq+mo7vXPYFQ6btcHToth92n/XyLQLGCMj9O2/quS9De5/opLJJoUFKp9k03lnhpQqWTTrArOVcb8lFbhiN1sLu0/4Co/x+Z6wSLvZWjgVCMGfrkcK+y/FXIlY+UqqG4yhQIyw262iQwGrkayeamQplXvoKewk/qZHiiuA3QJPHfRIcQ/x+smn+MUp/raM/qBFnuqrV4n9ixqR3DngOQezkeusV3vVIuOkBn3AyOH+Ki/vkY4orn6xNiAFo2FnNfviWUn4jS+pZD9vVpxoY4RKIrinkpDuFwkho/QBe/8G7CjNEj2alQ7fxpsVvi3FSbIRR9lfrfOXs/JSSzbwf4Z1vozwRyXxJoNSwPz2FL1tzCJgrzIoBWzKBgcyZE/iLmDhRwErHDAu+SyRBSw+BOycN4NrrgKWXk6h5J4Cxo5lifVdwJqKaRsVvw03K7yJiiIlJ/so/1nAot0FbOt1+t8QsOci/rOASd5MtCglTaB1fxGwtuagaMY5xN5zyXwO5d0VKY8eHqEUPofY+yz5KsTWXsrO1l7eiBfmOVRezFkZ9iZztix9D1+PoaOUsxztZz+GmNO/FDJny7Hlm/9wDOm6mzdHmPX5/5E1K2NpsjD5H6zZ5YR8Uv7r7vT+cFAlScqNSuLYLdXyRbLW+ZH4OWm3oVTUXUUcZHy0GxTmWCvs65cqe9KBOjhfVMQ5P15JALkEudRw0HweAb6Pt2NT9XCkJI74tIRm5zl9MSnHTzpCq/RhCF3miGt84R+Cq1MCJVvyX5TEmwTuPFG/6Ig3CVw6NT/6Gyy68EsCpXiVXbyuDzsbmb4AH/QiXtfWay1zjRts/4v128Wb/0qbeN1PeF1hQES5FYkX551cNcdB4nWXngzKho/I4yTxqupFvO6HeP2rwiGK/qM9/c+tWR765rl4xAzyB4Wj6v6RsDZoN7OWzLi9DPRL5TwyAYd+RocO9UzjHeq5cjuZz8ZcCPx0pfpDGyD77Vk85jcrFUtXs5Rze77kTmGaDyw1n7yH6CSe4yPzSSZ4S3AbdHKevHAg4Nwi9dBCxIMVtvj27B+A83Y9qsFfJIRZFLnuPCz8QZ1deFZ+o2V/vXAKdc2FaNAHf3l73APgW8yi7Ih6SKe3cjnuTbyfJno9+m/R+Y9rbOkD3PB1ZgHxbp3e6HiW/hoOTwCGx2jXq3cz2VDA8gFfhNIEwBBKGgQikmP4ZKcchLM7Ck38VpAO+vfAfmFjdpKXrQLtSoeu1qLrExGGC+vBuOtrcWrA51HUgiQI5VlW6pnJ/iA5EBOTb/u1z6BTsg67V5QXt4bOX4lsR3++XjScqQCYe9zmQOgBwQNzQemEPkN4NOTLoSyVw0uFrAboiahlFUXf98N6FGW2eYFx9j2v3WRZmToZOb6JdBy3+4ygXSu/nGOi7k7WL+qlkYVRsKU4JUm+RSZho+yaEX18tdF44Z4b/cu+u/xNioZaZF/ymlSvfAONCFBQIw3aXygaiEEKKz5E807zWRhQj42iQXDzJCcpU2aBwu8cDXOjSy2SHYPIf+dowMF4pGKdw8pLIO43iobZ/Pto+oAbjzK2TZAYfFE0sEYBWJKLeytww7QNeeFImbQNU1tM2oa8sm+pF2rTR2YCD9BHemWyXnXekkwNq2c1rssL/9UchZXNz1apnkR0S0uT51bsxeuzfpqJf0Jdm8/Grgq7UB8LR5EgeRKmrhutN6ZEDaH+BnlOct8qeEfm0YZasQt1XPhVOiPWTHQJJqLryUQk5DyhTL+3Eujyq9VjW6FMz1Hh8m+jkmR5rzINO+ewlbdXNZiu9w9qN7E3zMJaARttynT4LnGYZA+dTCR3y203pUWXDI4ViN7ZzIL0B6HuDBPQAFeYb/N0zuZJiSKGcb4MY+MkAkPRy/48zzZjgz15EnJskDbNNIHhafdOgw1l2x9ENcn4ccm1Fe5no2W7kmRPIhWmDmzDZ0GseDZgrwzagE2sK4kcUmEjORcyYb8eSpnTB1lNtDZmvuJbz2Qj+UZXqRracypyU+GKXdLSAzSZ6otUxadUzQ04MyaWIKSCdqejE172jCAf5IbJsh9UXmzAOQSh1XkIU8rwsJ9Ng+xQloJzvNs/tht9JFCWuHYmtSGpL31G/WIuQCHnqZlRrjs5Gvt6DhY54aZIcwSFLJoXNmsHlzSDqqrraaNYBZrR+1hQc3q83UrUvfVWknK+CjE2UoqrXv8gcg/MGK9rN6FXj7OPY/oscxzxhThrZWMnjyctRE3+fA5D9FAaB6DqurdziWaiiORzNWPbcrO2ClZ1P9vQXFyY+7bD1tFhi/2oxMrKplS9Y3o4LW/43Jk8jFKVDtu2MCd5kCth1Tz21KMnnWhKBevqz7RFe7CI563C5fCCEkMclGD8RWw6MCcU1vFc1zGk56i4s+5uiQ4PDq58PKnEYlmWomuyPEe1O7GbJCJHVHwx2+GoLKcXja32w3BtGdUPzbSoLUwMDwT1trB56CkUercQovuhxRa6u8kJuR+Gq5oyYY8A4Z45V18nkHSCtQb0qShGdaEF7cNwX8MYARk/SZu7s4lbA16thFWrqvzwUk68YnUJlAsz6SFxXzxfrS8d7xnymDtxidxE9vJ3XUl877r+Uqysh+f2oiqXcdwv46jbCfLUlYxoMdIuz+nlPG4Lhuf8i64kc3QlnoSKMknixxr0TVdSqgxjnzK5hXYTutYLqeKLrqTpwJZ5ghfUdp8FdjdebiMiXXSl7Gzl2PG95Imd4wuDjVCWafoIZxaO73a8nJuFvihLqp5hH6GuLIru4R8/leVi4F9p4QqFsUDVohzVi7KU6+rvl0MUDLAjzCPqqSufjEBG7X342Ed4V5Wipx6nwbo1N/0GFP9uzb2qysWwzumvPoJUlaOEeaXU2y26+2nRSVXJ/XqWSihBedxltc6I1uDxm6pS5vywPFHEluxOfmXY6Xiy80bUeVicuHGBmvU94hntLGkvIV7PDRL9Lw0SE1qYxxb6+ihlPaI46AUjPXOW6fQgnu/dclaS+c599aVnN3+p6krcV2LTVb5lXQJQSD7E9YN4WUFNiZWyU5HdH+xXYC2U85zHo7D4HPPguoVGEnl5Fjp2xa3Z37qYrBnNZL+GckgwXVXcAWyjokQJES5Z+6CdPv6yKj3CVWWsC1oAcXcIkze8gCBj5Kv3rkSyIXCqgzOT2pnMmBjOF4G0Q9A70T58B4qJf+hjBOGjhYZ/kE0ubNU5hdUAMGFsgsTRK5cmDX+Kb1nfHqGdpcvAYu0wx/tSWZo60dDa5hW+fqb0bHmFY14SKiSIUL7abxJXIwt3/tzF8CrotLSlmlKXuCdiEkCRYETUwzD73kKeurdv7fIkOE56zwIp9PhtOCe5UoUmDe1OtcaYDNUlwSu2GtodNPLT+E2+RqcpYVC7Ub29ciejHkxFhJ02ilIMKs0kETzAafwi5obZ1ehYal+CjebMSu57ODjL+sqacyaf0D4HDiojodG9lc6ezcEu0QurrnBPN5u4d4utAdsfWME0j8HYmteOVoY2QE5LsFo0qtw7IGfceHWmruAjdTZ9pe12LISkGLzBO83GVJajJIJn1QrGvNljS3L9Y9omoBN/1SaVl2sfsWpwoTZ3uGoY/6rAyD+Rba7ibGlVZx6L5jqLNl0vjWm0drOjFSo1UfNJkL1Uph35p8Y05zVjNKPLQLp3xgFs4f5bY5pprSMd0TIZf2xM83rG/mNjGjLB7x6aEibxHxrTtCEy91Z/0OuZoepKcnhKMT3qyYXNENJCErJaWcluSk709LtWJds2uhce173HRzsFqsGoe51KFq3AqWR1XTyuZSwM2cjaTHK4zMwseQebYxB8WHevWuqQcYY+ZPNGlIQ6oVzMlpyYMfCNzHnBmovIu2O25CUv3qklzb/yJHWC7fd6+MvsPElrWqtbd2JogYcW1lbHN9roAjsmWvUGbFQWnq2QhJoG5RysYPe43/Lev/ZCwvaD3Pt7aIFrUVB9NG+NmUTnGPbop72DZr8MIPZNpfuu59hut9EwcW4JMXXZ7hqjWdSVLXzgFCFEjdyOuxuC49qbCmDjx6VNBjxycC99OYC6HbZwftDoB7emfLnnkGwJxSpe1bWe1C05Bj2oRt4cmCwcGMaj4D4HKpK9ecc+qpgeo1rUOQTsjuw45lfREMqrIYSnZxbmzW6czwTdF1UJhhfzo3V3dqsdaF8y0W5pd98z1klY57943yIzJvPWW3Gaf45jz0W7lQw/z0AlCSkegpVPzxG4MT5n0bWjI22WxpZiICs6o6eSrfs5DdE3D9Bu0NKuIb6etBWQHQ4T+cXrFyQBItu4JkLcayp5SCrh1hbxCIlSySE9E9xhyUDOUT3rfHELAf5zetkX5ZKFzzK4FgW9o9gNSeaSzZdqvDs1oyxnyE/QWF4b306Iy+Zlu7RHiz+TyQNNhhIiUpVb7k8Ep9JLUjs+04+jMHCZ0xDSHiriNnUyqz0D+U+fGsYYsFuQWpKIfRlP9exVN/spIdePEF1KolDo1epuVKW1Gzpq+igaC0irCyUsxPSBNyxu4g2nTx17tN/IAH0kjuXF6sqyGdDmU4+l3gIqaMj1li0pbWbtkt8RS+3NYmHNfSFcW8e5prwOI772UXFOvXVjGi3vUMG/hFjd0mZvbs9JGMDDkKKBdwy4uUUNJMSS1pyOGy1nel/nLGriZXcyv3YnY/ZX8d5oMabk0bRZ8xTOD1dZJS4OzxT3AjD8Ks6DCstdPHxIzzVY4bFuUgbPcL/OrxpT0DRKHYaNw9KDmgjbgM1RiXD/HJXIjYq88tKVRq04PFQO+njmQUdjc2a4leHEYhZFh0nlt26QsBnKxZrYOXkCdKmeii6wmpCYDBCmurUxAo0e8ou5BrAJM3I1wg8JSMQ1RnEuDcLKal4ImOBLGzVzzEhc2Rt69l0mLIChUXUPvWAf8R2fgxKDAr36vekBc8tFD3TFhHlHkOizyzYbHU1YHZG3avQqVhRo75lH+m/LTaH+QKPjH7APr7mpgRT7yE3t0AcREo/P3BSi6j6MjkMgUrbcFGIy19zUNqqetJdJ4bfc1FG33BTaHFse35F4Tf2I4h7KI1WFNG9rqgrT+IUbbclwNBqa5c+ZKhrFsfTjA0q3/7+zq0uXG9WBe5kVYMA2LGYeMN3e/xKukdAf4E7mvuRLOue4wYCQVKWSqcgw7SsHpEpGMQBV5a+BKgXTH0da8LvbDXBVx/r6bWOAaB0DVSZMSfiTKo9V6ew6w0jLhfB7m6+GnV92cxqenRhJMkolrXpDb1te0gccxwFX8s/7dpZTfWRnCC1dDG8asE8yYAIWypot8DfjZf6Hw4elgbMQ8vuIgf9nOYFNbU9l+TR9LC0LOyqChPk5NBibn4Si49jOgWXUZPpMTUpm3q/lJ4FNsMjheP3CgO9ld01o7kFMWwkw4HIGWW7hW2yLUWEoZEdFFLTcIb1W/0Wg4YlhT3BT2NPqctQlrrnV2TlrRmE6lnkEsH+IM52Y7+EmMGiCEB66WCyhJRgWrvr40Ae6rGwvlL2kHYMExTLKWBcDWZlijFB2pkJGrgQf3GJ/5W1x67TtfEFtpo9EsaGtlbOzrlc76gUiOVUylSmfb+/XgoGkWxQfFeQVuDchRmb/ADxo77UyAJMqDO8QXzzs7jvHMEfuRmlTam/sAoGHYqE37VOjG6hhTx/sey24w7Z+Ru1ey7LDslSmXB9NpDnRPMO+KLO/3lbca6D+kBdt2kEmKY3r3v9WXzZ6W8UStVMd3LreEQ779bHiuMMXD/x9qJ6yXIEWxuXePVW1w5TejvrFwenG6qnTpjeYtDhEjglcLgWAMv1Kl1Uu+7BBTWcH2JfsexidYt8jw80gqboH6OSpQE5fk2oQPF72KD2owC4bQiNORRxvLIaOY4WJigYpNyb9RnuBneYik4QdpPX6oqhOk1jOtqivg1T59Y2c4AOUIV2VGwxo/ALr64ruRok7ROGY+K17ngQhn7fxbMqQe2P1qtPjsENUDVupJgU3fhqc7TkZTcd0mBPU1xma5Nup8KlCURo2KQ+6zWTGmjiDbjc50UD1dSLoAF7rMWYbwmZHIcCkrLsIPMgpFKXbwu+46AM7VxJTN8kD4FBkmku/PUcvmQJ6QBrCS8E4WD+juYIFfPtCmIl6OkKfR+gcCedvYxGmjYWZtlGYCZtxQfkF+3DQTRJWT/WV9DV+ieASv+acXE0sqRdzSYPFJiaLqye9H+VK3+1ROyx629+Fd/ZOLMZUg9N93JPClNYHyJCVQ6K3olk2taGpQFg2RbBcKaB4xWprS44hGy3JbgCGqCq3keEJtnnMVDAExd4JrRoyypHD7MlCjuH6JpU8aULd0I5Xc6K6c79PYClxS5nFchDiBnk3H6UjMB21jcs/CLe9CAmzRG2DooH3pHsBIxY3tKgNzvYHHimy/lqk3HcMxh5v/luhz3q/8Js9zljPE7bjmg35YajYI6Nkj70/uF/0HR0M6BPi3woBUy2AT2rErQxxVIq6CmBBL1Pw08iXsSCWu2kPLvinNNtFXEFVARdqD66uhyyjIhfw+MUSg0k2Akq5P2zOT0/dqJTSnzK8JVC22fTcZblzKTL2p+4O1dZUVN8/lNVUpJRjQUqRcGlALVZTiXsTNoRfr4aVUqiGncnH8WOcXrwEDt1reE5l9yvlesxqxOqkjeXtwPTviIjVeZKjIcbCR59+S4Mu5/gdkUeIr3Y/E6x3t5ijZi14aLBIUWNbiAw3ezfxu8M7nNM5eFXgSiqKkGRRIR7L1aTfAZRZ5Uuld9ho6nu4sgi/pjxM4nJpunAE7ofIcawhSqs0DGdJODqD7w/M7gIXCiKCAA3fxkWiIjv0tdvx0LhU4sT4x+kUPrCtlheTSVXd1WZ+CrjFHVjAUqaNorgp5Ooi52Pqp0DHb5v6KXtUVvA/5qqG3M+wSQv0WVmnfvI+p36yABk/UtHworLu0Nu+vXWDdFTCSszm91y02TKnLa/AoIvj2LdUNHgAcIOw59EyT2fAK99xuuava8o6Su3mdM22zkVXF1+LyuBK2G7TrPQtF62GtdepqMwJteZHMnouKotW+4C8jufvX2h5PCWj1zVllwIiT8V7/1E3oT2XAqxoXWJGF9z3d+XEUGSWT6OGMA7kz0VmTYffZg8EAhh8kh9FZjiMWNdtktupu6BdrLbvkB0jh9bpinbVr1SGBVmzwb5DpnCw75gpnKhsG1Zm/kfzrl381v8NreZk3jHXaM07JguJ9z49XmKtPAD1ZX4tC+sjyY7oLBQ7WMbWU0CRcxRGCvFIcrZCmLFHXX4y60ORGgqU06XW6cDEKJViGUkp9fhGcnlFK7syRH23rRL56J9gpXLASwyvGFbRgALtnEw51shZ3EhZZ1AtGjO9cuxhGeHuVcyv1ofnsEwXz/2QFoDvr/RvNvklir2yJayzIfbBBm3RSp0okU+nijVhY+XFxroiqiBl4oKaqEd7CrKblHZfOdKqerA1lViq5Plj1PHp6V4gkUi6t+8YDcYulIdmWesD05JyuZMaUW2omOI8qYjXXiHAwFZow5Ym+9XW/HI7b7LTYUOHpImRtN28rkPPb+hxqvRyemWQRMu5rAwYMqnjpHMDXTws2OrBOOLBMOjh29q312bhf9HisGQUGYXfDd8yG69aJYwMeFiqDqaJBswhK44iuIUiSDCZPGcFG1I/MGBZT7sd6qp1YY9wLgNngIEyUg6Ruj6Pdf0S8JRRFxTpUrpECpxjd8YBclPeTpdCqLMRPmcFaZV6py2IFbOULU5cXRz88TEEAjBtOO2guGNIgDrFYMoW1ByA53/rJi5WJU4M+DOgiqL9GXhkvobaNlvJmJNVJm+OqzZ4lZImas8f/CLKYELx08uIT+UaFwpTvbRIEQyIOt++bUb8C2iqCOIv3GXlYBV2JJXuGZamZg1oFNT2OK15K4r2rmMz8AG+JjbrMVxaaUuSyzLs7cH6nFyzaCx537X5lhjUYGVd9azvWkWsjbZeNRpwqMsA1K2qutkT5QQEK9M1UgYrw9JaVIe0gJobeO6QuPYLiakkOpBDycu5Lnlp6pZcefgmxXa5rFWrhkslLtDtMBbCcAHQadpOhEHzSrWy/ZI8ZAozspTsBi1WEWu7B4R8rPhZpXjaab9c4TtT4xA9z9tSa1rwkSAeP3B3ccfmsdmgIk+meXXXSWMtjQ752OzJ2ClVyOs3Csc2dJH0bknQ6itbAUDGCkA8Qgr+6DZwaA3SDJmxDi9xaZ+/Sb9UOfLz9qpK8ECY7Du9XqmNoW4sVpxRDavOw1LqkC0KJJ0q/+ck7eO5QcEfFtqbihijoQ1rerjXNYW10DEAXD1qf9WtTRvvEIX69TtElfTwZR7Ombbw4VLOHHV2F72YROOHVHFrkIVJYxu1LbYI1RzClyvILtBtQZAdwm1eNxnN71AhS+/eS7iN3SVILxPcJk7uRliwfKaTgV3trGp0LiPrUiWPWz66w226PXq8nmfGu+WZc0fZ+lgumnM0Smk9u21EaHf0ICEF2KgZnXLsWBULIzX5D+jR6QwhWX3SmitlIr68PXw7qjyclCe+f/F9TY13+fTfkwnu/r8mU2P882RCj/31ZLbwN5OJ6ddkph/fnavzY7EiytGI+j/5Ec+7/puh7PNE6bI/8O7aXEWWd4Wwtv1xoXDP+7Oprqz9vYKb6ChT0n/x3jISk+U5ruJzUOlIlaa1LEpoTrRTY73UojQklP75PKZtspa42FiXpLr+R59Tu9Ov7TOOIOhjcMJjmTTFdSBE18dUsgyomgE59a3MJXE0wRYU0ljavCuPgIhjQmCqJ0sX9tFt9fbb8ss9ria9jRT0IJnyXoHbg9/xLNjt/Z+nk9XbaAYCH3HW/PaLfSiPzy77KFXFQvOnP94WIONnLTXV49wWDsCP7JFzgbWo7+0ERw+MpIQ63nAmnk2e+f36it7pySDm6wg++HDglcJnZ6iQyDhcPwhyJcikacDP7K7lY2VOIfiipE1ufm/n4/Hij9d68SNOfgT9b64Xu4HNGsGPlX4aMz5uu0EQE9gtjcRy+ZvckAbeXh6tLxOs+yLefL2euNjo/zxDph+5cAMg0CSE4BA4+d8aUFwhvm6rLUdzbCtLFLQcOb4hgD5ru2ufD56nKZsWHC93cFeV5abLvjkZ8Ef9po5NhobM1m/ccEL1q4ZWsyyAytWAe3ny/K0V+nh+T/BzG/8c5c8g3VZvZfBJPQt+h0KvWu+o92EQWcuPcy+/nOSXqzax//z7Px03vIOEwgEA", - "entry_points_by_type": { - "EXTERNAL": [ - { - "selector": "0x22ff5f21f0b81b113e63f7db6da94fedef11b2119b4088b89664fb9a3cb658", - "function_idx": 0 - }, - { - "selector": "0x3c118a68e16e12e97ed25cb4901c12f4d3162818669cc44c391d8049924c14", - "function_idx": 3 - }, - { - "selector": "0x5562b3e932b4d139366854d5a2e578382e6a3b6572ac9943d55e7efbe43d00", - "function_idx": 9 - }, - { - "selector": "0x5df99ae77df976b4f0e5cf28c7dcfe09bd6e81aab787b19ac0c08e03d928cf", - "function_idx": 1 - }, - { - "selector": "0xb17d8a2731ba7ca1816631e6be14f0fc1b8390422d649fa27f0fbb0c91eea8", - "function_idx": 10 - }, - { - "selector": "0xe7510edcf6e9f1b70f7bd1f488767b50f0363422f3c563160ab77adf62467b", - "function_idx": 12 - }, - { - "selector": "0x169f135eddda5ab51886052d777a57f2ea9c162d713691b5e04a6d4ed71d47f", - "function_idx": 11 - }, - { - "selector": "0x27a4a7332e590dd789019a6d125ff2aacd358e453090978cbf81f0d85e4c045", - "function_idx": 2 - }, - { - "selector": "0x27c3334165536f239cfd400ed956eabff55fc60de4fb56728b6a4f6b87db01c", - "function_idx": 7 - }, - { - "selector": "0x2913ee03e5e3308c41e308bd391ea4faac9b9cb5062c76a6b3ab4f65397e106", - "function_idx": 4 - }, - { - "selector": "0x2d7cf5d5a324a320f9f37804b1615a533fde487400b41af80f13f7ac5581325", - "function_idx": 5 - }, - { - "selector": "0x31aafc75f498fdfa7528880ad27246b4c15af4954f96228c9a132b328de1c92", - "function_idx": 6 - }, - { - "selector": "0x3604cea1cdb094a73a31144f14a3e5861613c008e1e879939ebc4827d10cd50", - "function_idx": 8 - } - ], - "L1_HANDLER": [], - "CONSTRUCTOR": [ - { - "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", - "function_idx": 13 - } - ] - }, - "abi": "[{\"type\": \"constructor\", \"name\": \"constructor\", \"inputs\": []}, {\"type\": \"function\", \"name\": \"test\", \"inputs\": [{\"name\": \"arg\", \"type\": \"core::felt252\"}, {\"name\": \"arg1\", \"type\": \"core::felt252\"}, {\"name\": \"arg2\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_storage_read\", \"inputs\": [{\"name\": \"address\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_storage_write\", \"inputs\": [{\"name\": \"address\", \"type\": \"core::felt252\"}, {\"name\": \"value\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_get_execution_info\", \"inputs\": [{\"name\": \"block_number\", \"type\": \"core::felt252\"}, {\"name\": \"block_timestamp\", \"type\": \"core::felt252\"}, {\"name\": \"sequencer_address\", \"type\": \"core::felt252\"}, {\"name\": \"version\", \"type\": \"core::felt252\"}, {\"name\": \"account_address\", \"type\": \"core::felt252\"}, {\"name\": \"max_fee\", \"type\": \"core::felt252\"}, {\"name\": \"chain_id\", \"type\": \"core::felt252\"}, {\"name\": \"nonce\", \"type\": \"core::felt252\"}, {\"name\": \"caller_address\", \"type\": \"core::felt252\"}, {\"name\": \"contract_address\", \"type\": \"core::felt252\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_emit_event\", \"inputs\": [{\"name\": \"keys\", \"type\": \"core::array::Array::\"}, {\"name\": \"data\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_send_message_to_l1\", \"inputs\": [{\"name\": \"to_address\", \"type\": \"core::felt252\"}, {\"name\": \"payload\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_emit_simple_event\", \"inputs\": [{\"name\": \"argument\", \"type\": \"core::felt252\"}, {\"name\": \"my_array\", \"type\": \"core::array::Array::\"}, {\"name\": \"another_argument\", \"type\": \"core::felt252\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_call_contract\", \"inputs\": [{\"name\": \"contract_address\", \"type\": \"core::starknet::contract_address::ContractAddress\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_library_call\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}, {\"name\": \"entry_point_selector\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"assert_eq\", \"inputs\": [{\"name\": \"x\", \"type\": \"core::felt252\"}, {\"name\": \"y\", \"type\": \"core::felt252\"}], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_segment_arena\", \"inputs\": [], \"outputs\": [{\"type\": \"core::felt252\"}], \"state_mutability\": \"external\"}, {\"type\": \"enum\", \"name\": \"core::bool\", \"variants\": [{\"name\": \"False\", \"type\": \"()\"}, {\"name\": \"True\", \"type\": \"()\"}]}, {\"type\": \"function\", \"name\": \"test_deploy\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}, {\"name\": \"contract_address_salt\", \"type\": \"core::felt252\"}, {\"name\": \"calldata\", \"type\": \"core::array::Array::\"}, {\"name\": \"deploy_from_zero\", \"type\": \"core::bool\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"function\", \"name\": \"test_replace_class\", \"inputs\": [{\"name\": \"class_hash\", \"type\": \"core::starknet::class_hash::ClassHash\"}], \"outputs\": [], \"state_mutability\": \"external\"}, {\"type\": \"event\", \"name\": \"test_contract::test_contract_cairo1::TestContract::simple_event\", \"kind\": \"struct\", \"members\": [{\"name\": \"argument\", \"type\": \"core::felt252\", \"kind\": \"data\"}, {\"name\": \"my_array\", \"type\": \"core::array::Array::\", \"kind\": \"data\"}]}, {\"type\": \"event\", \"name\": \"test_contract::test_contract_cairo1::TestContract::Event\", \"kind\": \"enum\", \"variants\": [{\"name\": \"simple_event\", \"type\": \"test_contract::test_contract_cairo1::TestContract::simple_event\", \"kind\": \"nested\"}]}]" - }, - "nonce": "0x0", - "resource_bounds": { - "L1_GAS": { - "max_amount": "0x5", - "max_price_per_unit": "0x6" - }, - "L2_GAS": { - "max_amount": "0x0", - "max_price_per_unit": "0x0" - } - }, - "sender_address": "0x1b34d819720bd84c89bdfb476bc2c4d0de9a41b766efabd20fa292280e4c6d9", - "signature": [ - "0x1132577", - "0x17df53c" - ], - "type": "DECLARE", - "version": "0x3", - "tip": "0x0", - "account_deployment_data": [], - "fee_data_availability_mode": 0, - "nonce_data_availability_mode": 0, - "paymaster_data": [] -} diff --git a/crates/gateway/tests/fixtures/deploy_account_v3.json b/crates/gateway/tests/fixtures/deploy_account_v3.json deleted file mode 100644 index ced06cad516..00000000000 --- a/crates/gateway/tests/fixtures/deploy_account_v3.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "class_hash": "0x1fcd2dcd811e049eaa730b87038a68f39220a04986b43fe287d690da0df01b8", - "constructor_calldata": [ - "0x0", - "0x1", - "0x2", - "0x3", - "0x4", - "0x5", - "0x6", - "0x7", - "0x8", - "0x9" - ], - "contract_address_salt": "0x2", - "nonce": "0x0", - "resource_bounds": { - "L1_GAS": { - "max_amount": "0x5", - "max_price_per_unit": "0x6" - }, - "L2_GAS": { - "max_amount": "0x0", - "max_price_per_unit": "0x0" - } - }, - "signature": [ - "0x1132577", - "0x17df53c" - ], - "type": "DEPLOY_ACCOUNT", - "version": "0x3", - "tip": "0x0", - "nonce_data_availability_mode": 0, - "fee_data_availability_mode": 0, - "paymaster_data": [] -} diff --git a/crates/gateway/tests/fixtures/invoke_v3.json b/crates/gateway/tests/fixtures/invoke_v3.json deleted file mode 100644 index ad126901aa1..00000000000 --- a/crates/gateway/tests/fixtures/invoke_v3.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "calldata": [ - "0x0", - "0x1", - "0x2", - "0x3", - "0x4", - "0x5", - "0x6", - "0x7", - "0x8", - "0x9" - ], - "nonce": "0x0", - "resource_bounds": { - "L1_GAS": { - "max_amount": "0x5", - "max_price_per_unit": "0x6" - }, - "L2_GAS": { - "max_amount": "0x0", - "max_price_per_unit": "0x0" - } - }, - "sender_address": "0x1b34d819720bd84c89bdfb476bc2c4d0de9a41b766efabd20fa292280e4c6d9", - "signature": [ - "0x1132577", - "0x17df53c" - ], - "type": "INVOKE_FUNCTION", - "version": "0x3", - "tip": "0x0", - "nonce_data_availability_mode": 1, - "fee_data_availability_mode": 1, - "paymaster_data": [], - "account_deployment_data": [] -} diff --git a/crates/gateway/tests/routing_test.rs b/crates/gateway/tests/routing_test.rs deleted file mode 100644 index 3855c28d7fc..00000000000 --- a/crates/gateway/tests/routing_test.rs +++ /dev/null @@ -1,49 +0,0 @@ -use std::fs; -use std::path::Path; - -use axum::body::{Body, Bytes, HttpBody}; -use axum::http::{Request, StatusCode}; -use pretty_assertions::assert_str_eq; -use rstest::rstest; -use starknet_gateway::gateway::app; -use tower::ServiceExt; - -const TEST_FILES_FOLDER: &str = "./tests/fixtures"; - -// TODO(Ayelet): Replace the use of the JSON files with generated instances, then serialize these -// into JSON for testing. -#[rstest] -#[case::declare(&Path::new(TEST_FILES_FOLDER).join("declare_v3.json"), "DECLARE")] -#[case::deploy_account( - &Path::new(TEST_FILES_FOLDER).join("deploy_account_v3.json"), - "DEPLOY_ACCOUNT" -)] -#[case::invoke(&Path::new(TEST_FILES_FOLDER).join("invoke_v3.json"), "INVOKE")] -#[tokio::test] -async fn test_routes(#[case] json_file_path: &Path, #[case] expected_response: &str) { - let tx_json = fs::read_to_string(json_file_path).unwrap(); - let request = Request::post("/add_transaction") - .header("content-type", "application/json") - .body(Body::from(tx_json)) - .unwrap(); - - let response = check_request(request, StatusCode::OK).await; - - assert_str_eq!(expected_response, String::from_utf8_lossy(&response)); -} - -#[tokio::test] -#[should_panic] -// FIXME: Currently is_alive is not implemented, fix this once it is implemented. -async fn test_is_alive() { - let request = Request::get("/is_alive").body(Body::empty()).unwrap(); - // Status code doesn't matter, this panics ATM. - check_request(request, StatusCode::default()).await; -} - -async fn check_request(request: Request, status_code: StatusCode) -> Bytes { - let response = app().oneshot(request).await.unwrap(); - assert_eq!(response.status(), status_code); - - response.into_body().collect().await.unwrap().to_bytes() -} diff --git a/crates/mempool/Cargo.toml b/crates/mempool/Cargo.toml index c850cf56e13..248471ae132 100644 --- a/crates/mempool/Cargo.toml +++ b/crates/mempool/Cargo.toml @@ -9,11 +9,19 @@ license.workspace = true workspace = true [dependencies] +anyhow.workspace = true +async-trait.workspace = true derive_more.workspace = true +mempool_infra = { path = "../mempool_infra", version = "0.4.0-dev.2"} # TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. -starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", rev = "1b46b42" } -thiserror.workspace = true +starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "main-mempool" } +starknet_mempool_types = { path = "../mempool_types", version = "0.4.0-dev.2"} +tokio.workspace = true [dev-dependencies] assert_matches.workspace = true +pretty_assertions.workspace = true +rstest.workspace = true +# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. +starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "main-mempool", features = ["testing"] } tokio.workspace = true diff --git a/crates/mempool/src/errors.rs b/crates/mempool/src/errors.rs deleted file mode 100644 index 4bbd7855896..00000000000 --- a/crates/mempool/src/errors.rs +++ /dev/null @@ -1,4 +0,0 @@ -use thiserror::Error; - -#[derive(Debug, Error)] -pub enum MempoolError {} diff --git a/crates/mempool/src/lib.rs b/crates/mempool/src/lib.rs index 177671c3be5..fb92ed2d30a 100644 --- a/crates/mempool/src/lib.rs +++ b/crates/mempool/src/lib.rs @@ -1,4 +1,2 @@ -// TODO: change to pub(crate) once this is used by the (not yet implemented) mempool struct. -pub mod errors; pub mod mempool; -pub mod priority_queue; +pub(crate) mod priority_queue; diff --git a/crates/mempool/src/mempool.rs b/crates/mempool/src/mempool.rs index 6b2e70c5d45..32750192936 100644 --- a/crates/mempool/src/mempool.rs +++ b/crates/mempool/src/mempool.rs @@ -1,32 +1,112 @@ +use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; +use anyhow::Result; +use async_trait::async_trait; +use mempool_infra::component_server::ComponentRequestHandler; +use mempool_infra::network_component::CommunicationInterface; use starknet_api::core::ContractAddress; -use starknet_api::internal_transaction::InternalTransaction; use starknet_api::transaction::TransactionHash; +use starknet_mempool_types::errors::MempoolError; +use starknet_mempool_types::mempool_types::{ + Account, + AccountState, + BatcherToMempoolChannels, + BatcherToMempoolMessage, + GatewayToMempoolMessage, + MempoolInput, + MempoolNetworkComponent, + MempoolRequest, + MempoolResponse, + MempoolResult, + ThinTransaction, +}; +use tokio::select; -use crate::errors::MempoolError; +use crate::priority_queue::TransactionPriorityQueue; -pub type MempoolResult = Result; +#[cfg(test)] +#[path = "mempool_test.rs"] +pub mod mempool_test; -pub struct Mempool; +pub struct Mempool { + // TODO: add docstring explaining visibility and coupling of the fields. + pub gateway_network: MempoolNetworkComponent, + batcher_network: BatcherToMempoolChannels, + txs_queue: TransactionPriorityQueue, + state: HashMap, +} impl Mempool { + pub fn new( + inputs: impl IntoIterator, + gateway_network: MempoolNetworkComponent, + batcher_network: BatcherToMempoolChannels, + ) -> Self { + let mut mempool = Mempool { + txs_queue: TransactionPriorityQueue::default(), + state: HashMap::default(), + gateway_network, + batcher_network, + }; + + mempool.txs_queue = TransactionPriorityQueue::from( + inputs + .into_iter() + .map(|input| { + // Attempts to insert a key-value pair into the mempool's state. Returns `None` + // if the key was not present, otherwise returns the old value while updating + // the new value. + let prev_value = + mempool.state.insert(input.account.address, input.account.state); + assert!( + prev_value.is_none(), + "Sender address: {:?} already exists in the mempool. Can't add {:?} to \ + the mempool.", + input.account.address, + input.tx + ); + input.tx + }) + .collect::>(), + ); + + mempool + } + + pub fn empty( + gateway_network: MempoolNetworkComponent, + batcher_network: BatcherToMempoolChannels, + ) -> Self { + Mempool::new([], gateway_network, batcher_network) + } + /// Retrieves up to `n_txs` transactions with the highest priority from the mempool. /// Transactions are guaranteed to be unique across calls until `commit_block` is invoked. // TODO: the last part about commit_block is incorrect if we delete txs in get_txs and then push - // back. - pub fn get_txs(_n_txs: u8) -> MempoolResult> { - todo!(); + // back. TODO: Consider renaming to `pop_txs` to be more consistent with the standard + // library. + pub fn get_txs(&mut self, n_txs: usize) -> MempoolResult> { + let txs = self.txs_queue.pop_last_chunk(n_txs); + for tx in &txs { + self.state.remove(&tx.sender_address); + } + + Ok(txs) } /// Adds a new transaction to the mempool. /// TODO: support fee escalation and transactions with future nonces. - pub fn add_tx( - &mut self, - _tx: InternalTransaction, - _account_state: AccountState, - ) -> MempoolResult<()> { - todo!(); + /// TODO: change input type to `MempoolInput`. + pub fn add_tx(&mut self, tx: ThinTransaction, account: Account) -> MempoolResult<()> { + match self.state.entry(account.address) { + Occupied(_) => Err(MempoolError::DuplicateTransaction { tx_hash: tx.tx_hash }), + Vacant(entry) => { + entry.insert(account.state); + self.txs_queue.push(tx); + Ok(()) + } + } } /// Update the mempool's internal state according to the committed block's transactions. @@ -41,6 +121,83 @@ impl Mempool { ) -> MempoolResult<()> { todo!() } + + /// Listens asynchronously for network messages and processes them. + pub async fn run(&mut self) -> Result<()> { + loop { + select! { + optional_gateway_message = self.gateway_network.recv() => { + match optional_gateway_message { + Some(message) => { + self.process_gateway_message(message)?; + }, + // Channel was closed; exit. + None => break, + } + } + optional_batcher_message = self.batcher_network.rx.recv() => { + match optional_batcher_message { + Some(message) => { + self.process_batcher_message(message).await?; + }, + // Channel was closed; exit. + None => break, + } + } + } + } + Ok(()) + } + + fn process_gateway_message(&mut self, message: GatewayToMempoolMessage) -> Result<()> { + match message { + GatewayToMempoolMessage::AddTransaction(mempool_input) => { + self.add_tx(mempool_input.tx, mempool_input.account)?; + Ok(()) + } + } + } + + async fn process_batcher_message(&mut self, message: BatcherToMempoolMessage) -> Result<()> { + match message { + BatcherToMempoolMessage::GetTransactions(n_txs) => { + let txs = self.get_txs(n_txs)?; + self.batcher_network.tx.send(txs).await?; + Ok(()) + } + } + } +} + +/// Wraps the mempool to enable inbound async communication from other components. +pub struct MempoolCommunicationWrapper { + mempool: Mempool, } -pub struct AccountState; +impl MempoolCommunicationWrapper { + pub fn new(mempool: Mempool) -> Self { + MempoolCommunicationWrapper { mempool } + } + + fn add_tx(&mut self, mempool_input: MempoolInput) -> MempoolResult<()> { + self.mempool.add_tx(mempool_input.tx, mempool_input.account) + } + + fn get_txs(&mut self, n_txs: usize) -> MempoolResult> { + self.mempool.get_txs(n_txs) + } +} + +#[async_trait] +impl ComponentRequestHandler for MempoolCommunicationWrapper { + async fn handle_request(&mut self, request: MempoolRequest) -> MempoolResponse { + match request { + MempoolRequest::AddTransaction(mempool_input) => { + MempoolResponse::AddTransaction(self.add_tx(mempool_input)) + } + MempoolRequest::GetTransactions(n_txs) => { + MempoolResponse::GetTransactions(self.get_txs(n_txs)) + } + } + } +} diff --git a/crates/mempool/src/mempool_test.rs b/crates/mempool/src/mempool_test.rs new file mode 100644 index 00000000000..54b6daaf237 --- /dev/null +++ b/crates/mempool/src/mempool_test.rs @@ -0,0 +1,160 @@ +use assert_matches::assert_matches; +use pretty_assertions::assert_eq; +use rstest::{fixture, rstest}; +use starknet_api::core::{ContractAddress, PatriciaKey}; +use starknet_api::hash::{StarkFelt, StarkHash}; +use starknet_api::transaction::{Tip, TransactionHash}; +use starknet_api::{contract_address, patricia_key}; +use starknet_mempool_types::errors::MempoolError; +use starknet_mempool_types::mempool_types::{ + BatcherToMempoolChannels, + BatcherToMempoolMessage, + GatewayToMempoolMessage, + MempoolNetworkComponent, + MempoolToBatcherMessage, + MempoolToGatewayMessage, + ThinTransaction, +}; +use starknet_mempool_types::utils::create_thin_tx_for_testing; +use tokio::sync::mpsc::channel; + +use crate::mempool::{Account, Mempool, MempoolInput}; + +/// Creates a valid input for mempool's `add_tx` with optional default value for +/// `sender_address`. +/// Usage: +/// 1. add_tx_input!(tip, tx_hash, address) +/// 2. add_tx_input!(tip, tx_hash) +// TODO: Return MempoolInput once it's used in `add_tx`. +macro_rules! add_tx_input { + ($tip:expr, $tx_hash:expr, $address:expr) => {{ + let account = Account { address: $address, ..Default::default() }; + let tx = create_thin_tx_for_testing($tip, $tx_hash, $address); + (tx, account) + }}; + ($tip:expr, $tx_hash:expr) => { + add_tx_input!($tip, $tx_hash, ContractAddress::default()) + }; +} + +#[fixture] +fn mempool() -> Mempool { + create_for_testing([]) +} + +#[rstest] +#[case(3)] // Requesting exactly the number of transactions in the queue +#[case(5)] // Requesting more transactions than are in the queue +#[case(2)] // Requesting fewer transactions than are in the queue +fn test_get_txs(#[case] requested_txs: usize) { + let (tx_tip_50_address_0, account1) = add_tx_input!(Tip(50), TransactionHash(StarkFelt::ONE)); + let (tx_tip_100_address_1, account2) = + add_tx_input!(Tip(100), TransactionHash(StarkFelt::TWO), contract_address!("0x1")); + let (tx_tip_10_address_2, account3) = + add_tx_input!(Tip(10), TransactionHash(StarkFelt::THREE), contract_address!("0x2")); + + let mut mempool = create_for_testing([ + MempoolInput { tx: tx_tip_50_address_0.clone(), account: account1 }, + MempoolInput { tx: tx_tip_100_address_1.clone(), account: account2 }, + MempoolInput { tx: tx_tip_10_address_2.clone(), account: account3 }, + ]); + + let expected_addresses = + vec![contract_address!("0x0"), contract_address!("0x1"), contract_address!("0x2")]; + // checks that the transactions were added to the mempool. + for address in &expected_addresses { + assert!(mempool.state.contains_key(address)); + } + + let sorted_txs = vec![tx_tip_100_address_1, tx_tip_50_address_0, tx_tip_10_address_2]; + + let txs = mempool.get_txs(requested_txs).unwrap(); + + // This ensures we do not exceed the priority queue's limit of 3 transactions. + let max_requested_txs = requested_txs.min(3); + + // checks that the returned transactions are the ones with the highest priority. + assert_eq!(txs.len(), max_requested_txs); + assert_eq!(txs, sorted_txs[..max_requested_txs].to_vec()); + + // checks that the transactions that were not returned are still in the mempool. + let actual_addresses: Vec<&ContractAddress> = mempool.state.keys().collect(); + let expected_remaining_addresses: Vec<&ContractAddress> = + expected_addresses[max_requested_txs..].iter().collect(); + assert_eq!(actual_addresses, expected_remaining_addresses,); +} + +#[rstest] +#[should_panic(expected = "Sender address: \ + ContractAddress(PatriciaKey(StarkFelt(\"\ + 0x0000000000000000000000000000000000000000000000000000000000000000\"\ + ))) already exists in the mempool. Can't add")] +fn test_mempool_initialization_with_duplicate_sender_addresses() { + let (tx, account) = add_tx_input!(Tip(50), TransactionHash(StarkFelt::ONE)); + let same_tx = tx.clone(); + + let inputs = vec![MempoolInput { tx, account }, MempoolInput { tx: same_tx, account }]; + + // This call should panic because of duplicate sender addresses + let _mempool = create_for_testing(inputs.into_iter()); +} + +#[rstest] +fn test_add_tx(mut mempool: Mempool) { + let (tx_tip_50_address_0, account1) = add_tx_input!(Tip(50), TransactionHash(StarkFelt::ONE)); + let (tx_tip_100_address_1, account2) = + add_tx_input!(Tip(100), TransactionHash(StarkFelt::TWO), contract_address!("0x1")); + let (tx_tip_80_address_2, account3) = + add_tx_input!(Tip(80), TransactionHash(StarkFelt::THREE), contract_address!("0x2")); + + assert_matches!(mempool.add_tx(tx_tip_50_address_0.clone(), account1), Ok(())); + assert_matches!(mempool.add_tx(tx_tip_100_address_1.clone(), account2), Ok(())); + assert_matches!(mempool.add_tx(tx_tip_80_address_2.clone(), account3), Ok(())); + + assert_eq!(mempool.state.len(), 3); + mempool.state.contains_key(&account1.address); + mempool.state.contains_key(&account2.address); + mempool.state.contains_key(&account3.address); + + check_mempool_txs_eq( + &mempool, + &[tx_tip_50_address_0, tx_tip_80_address_2, tx_tip_100_address_1], + ) +} + +#[rstest] +fn test_add_same_tx(mut mempool: Mempool) { + let (tx, account) = add_tx_input!(Tip(50), TransactionHash(StarkFelt::ONE)); + let same_tx = tx.clone(); + + assert_matches!(mempool.add_tx(tx.clone(), account), Ok(())); + assert_matches!( + mempool.add_tx(same_tx, account), + Err(MempoolError::DuplicateTransaction { tx_hash: TransactionHash(StarkFelt::ONE) }) + ); + // Assert that the original tx remains in the pool after the failed attempt. + check_mempool_txs_eq(&mempool, &[tx]) +} + +// Asserts that the transactions in the mempool are in ascending order as per the expected +// transactions. +fn check_mempool_txs_eq(mempool: &Mempool, expected_txs: &[ThinTransaction]) { + let mempool_txs = mempool.txs_queue.iter(); + // Deref the inner mempool tx type. + expected_txs.iter().zip(mempool_txs).all(|(a, b)| *a == **b); +} + +// TODO: remove network code once server abstraction is merged, then move into mempool with cfg. +fn create_for_testing(inputs: impl IntoIterator) -> Mempool { + let (_, rx_gateway_to_mempool) = channel::(1); + let (tx_mempool_to_gateway, _) = channel::(1); + let gateway_network = + MempoolNetworkComponent::new(tx_mempool_to_gateway, rx_gateway_to_mempool); + + let (_, rx_mempool_to_batcher) = channel::(1); + let (tx_batcher_to_mempool, _) = channel::(1); + let batcher_network = + BatcherToMempoolChannels { rx: rx_mempool_to_batcher, tx: tx_batcher_to_mempool }; + + Mempool::new(inputs, gateway_network, batcher_network) +} diff --git a/crates/mempool/src/priority_queue.rs b/crates/mempool/src/priority_queue.rs index 0d04818c544..967cb1a8d1f 100644 --- a/crates/mempool/src/priority_queue.rs +++ b/crates/mempool/src/priority_queue.rs @@ -1,83 +1,57 @@ -#[cfg(test)] -#[path = "priority_queue_test.rs"] -pub mod priority_queue_test; - use std::cmp::Ordering; use std::collections::BTreeSet; -use starknet_api::internal_transaction::InternalTransaction; -use starknet_api::transaction::{ - DeclareTransaction, - DeployAccountTransaction, - InvokeTransaction, - Tip, -}; +use starknet_mempool_types::mempool_types::ThinTransaction; // Assumption: for the MVP only one transaction from the same contract class can be in the mempool // at a time. When this changes, saving the transactions themselves on the queu might no longer be // appropriate, because we'll also need to stores transactions without indexing them. For example, // transactions with future nonces will need to be stored, and potentially indexed on block commits. #[derive(Clone, Debug, Default, derive_more::Deref, derive_more::DerefMut)] -pub struct PriorityQueue(BTreeSet); +pub struct TransactionPriorityQueue(BTreeSet); -impl PriorityQueue { - pub fn push(&mut self, tx: InternalTransaction) { - let mempool_tx = PQTransaction(tx); +impl TransactionPriorityQueue { + pub fn push(&mut self, tx: ThinTransaction) { + let mempool_tx = PrioritizedTransaction(tx); self.insert(mempool_tx); } - // Removes and returns the transaction with the highest tip. - pub fn pop(&mut self) -> Option { - self.pop_last().map(|tx| tx.0) + // TODO(gilad): remove collect + pub fn pop_last_chunk(&mut self, n_txs: usize) -> Vec { + (0..n_txs).filter_map(|_| self.pop_last().map(|tx| tx.0)).collect() } } -impl From> for PriorityQueue { - fn from(transactions: Vec) -> Self { - PriorityQueue(BTreeSet::from_iter(transactions.into_iter().map(PQTransaction))) +impl From> for TransactionPriorityQueue { + fn from(transactions: Vec) -> Self { + TransactionPriorityQueue(BTreeSet::from_iter( + transactions.into_iter().map(PrioritizedTransaction), + )) } } #[derive(Clone, Debug, derive_more::Deref, derive_more::From)] -pub struct PQTransaction(pub InternalTransaction); - -impl PQTransaction { - fn tip(&self) -> Tip { - match &self.0 { - InternalTransaction::Declare(declare_tx) => match &declare_tx.tx { - DeclareTransaction::V3(tx_v3) => tx_v3.tip, - _ => unimplemented!(), - }, - InternalTransaction::DeployAccount(deploy_account_tx) => match &deploy_account_tx.tx { - DeployAccountTransaction::V3(tx_v3) => tx_v3.tip, - _ => unimplemented!(), - }, - InternalTransaction::Invoke(invoke_tx) => match &invoke_tx.tx { - InvokeTransaction::V3(tx_v3) => tx_v3.tip, - _ => unimplemented!(), - }, - } - } -} +pub struct PrioritizedTransaction(pub ThinTransaction); -// Compare transactions based on their tip only, which implies `Eq`, because `tip` is uint. -impl PartialEq for PQTransaction { - fn eq(&self, other: &PQTransaction) -> bool { - self.tip() == other.tip() +/// Compare transactions based only on their tip, a uint, using the Eq trait. It ensures that two +/// tips are either exactly equal or not. +impl PartialEq for PrioritizedTransaction { + fn eq(&self, other: &PrioritizedTransaction) -> bool { + self.tip == other.tip } } -/// Marks PQTransaction as capable of strict equality comparisons, signaling to the compiler it +/// Marks this struct as capable of strict equality comparisons, signaling to the compiler it /// adheres to equality semantics. // Note: this depends on the implementation of `PartialEq`, see its docstring. -impl Eq for PQTransaction {} +impl Eq for PrioritizedTransaction {} -impl Ord for PQTransaction { +impl Ord for PrioritizedTransaction { fn cmp(&self, other: &Self) -> Ordering { - self.tip().cmp(&other.tip()) + self.tip.cmp(&other.tip) } } -impl PartialOrd for PQTransaction { +impl PartialOrd for PrioritizedTransaction { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } diff --git a/crates/mempool/src/priority_queue_test.rs b/crates/mempool/src/priority_queue_test.rs deleted file mode 100644 index 78c9425e5eb..00000000000 --- a/crates/mempool/src/priority_queue_test.rs +++ /dev/null @@ -1,58 +0,0 @@ -use starknet_api::data_availability::DataAvailabilityMode; -use starknet_api::hash::StarkFelt; -use starknet_api::internal_transaction::{InternalInvokeTransaction, InternalTransaction}; -use starknet_api::transaction::{ - InvokeTransaction, - InvokeTransactionV3, - ResourceBounds, - ResourceBoundsMapping, - Tip, - TransactionHash, -}; - -use crate::priority_queue::PriorityQueue; - -pub fn create_tx_for_testing(tip: Tip, tx_hash: TransactionHash) -> InternalTransaction { - let tx = InvokeTransactionV3 { - resource_bounds: ResourceBoundsMapping::try_from(vec![ - (starknet_api::transaction::Resource::L1Gas, ResourceBounds::default()), - (starknet_api::transaction::Resource::L2Gas, ResourceBounds::default()), - ]) - .expect("Resource bounds mapping has unexpected structure."), - signature: Default::default(), - nonce: Default::default(), - sender_address: Default::default(), - calldata: Default::default(), - nonce_data_availability_mode: DataAvailabilityMode::L1, - fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: Default::default(), - account_deployment_data: Default::default(), - tip, - }; - - InternalTransaction::Invoke(InternalInvokeTransaction { - tx: InvokeTransaction::V3(tx), - tx_hash, - only_query: false, - }) -} - -#[tokio::test] -async fn test_priority_queue() { - let tx_hash_50 = TransactionHash(StarkFelt::ONE); - let tx_hash_100 = TransactionHash(StarkFelt::TWO); - let tx_hash_10 = TransactionHash(StarkFelt::THREE); - - let tx_tip_50 = create_tx_for_testing(Tip(50), tx_hash_50); - let tx_tip_100 = create_tx_for_testing(Tip(100), tx_hash_100); - let tx_tip_10 = create_tx_for_testing(Tip(10), tx_hash_10); - - let mut pq = PriorityQueue::default(); - pq.push(tx_tip_50.clone()); - pq.push(tx_tip_100.clone()); - pq.push(tx_tip_10.clone()); - - assert_eq!(pq.pop().unwrap(), tx_tip_100); - assert_eq!(pq.pop().unwrap(), tx_tip_50); - assert_eq!(pq.pop().unwrap(), tx_tip_10); -} diff --git a/crates/mempool_infra/Cargo.toml b/crates/mempool_infra/Cargo.toml index 58359b41450..589f1a9708d 100644 --- a/crates/mempool_infra/Cargo.toml +++ b/crates/mempool_infra/Cargo.toml @@ -13,7 +13,20 @@ workspace = true [dependencies] async-trait.workspace = true +serde.workspace = true +papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } thiserror.workspace = true tokio.workspace = true +tonic = "0.11.0" +prost = "0.12.6" +[dev-dependencies] +assert_matches.workspace = true +pretty_assertions.workspace = true +[build-dependencies] +protoc-prebuilt = "0.3.0" +tonic-build = "0.11.0" + +[package.metadata.cargo-machete] +ignored = ["prost"] diff --git a/crates/mempool_infra/build.rs b/crates/mempool_infra/build.rs new file mode 100644 index 00000000000..bec4b5d6eed --- /dev/null +++ b/crates/mempool_infra/build.rs @@ -0,0 +1,18 @@ +use std::env::set_var; + +use protoc_prebuilt::init; +use tonic_build::{configure, Builder}; + +fn main() { + set_var("PROTOC_PREBUILT_NOT_ADD_GITHUB_TOKEN", "true"); + let (protoc_bin, _) = init("27.0").unwrap(); + set_var("PROTOC", protoc_bin); + + let builder: Builder = configure(); + builder + .compile( + &["proto/component_a_service.proto", "proto/component_b_service.proto"], + &["proto"], + ) + .unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e)); +} diff --git a/crates/mempool_infra/proto/component_a_service.proto b/crates/mempool_infra/proto/component_a_service.proto new file mode 100644 index 00000000000..66fda3c1bef --- /dev/null +++ b/crates/mempool_infra/proto/component_a_service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package component_a_service; + +service ComponentA { + rpc GetValue(ComponentAMessage) returns (ComponentAResponse) {} +} + +message ComponentAMessage { +} + +message ComponentAResponse { + int32 value = 1; +} diff --git a/crates/mempool_infra/proto/component_b_service.proto b/crates/mempool_infra/proto/component_b_service.proto new file mode 100644 index 00000000000..e8e736026ed --- /dev/null +++ b/crates/mempool_infra/proto/component_b_service.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; + +package component_b_service; + +service ComponentB { + rpc GetValue(ComponentBMessage) returns (ComponentBResponse) {} +} + +message ComponentBMessage { +} + +message ComponentBResponse { + uint32 value = 1; +} diff --git a/crates/mempool_infra/src/component_client.rs b/crates/mempool_infra/src/component_client.rs new file mode 100644 index 00000000000..b348e087272 --- /dev/null +++ b/crates/mempool_infra/src/component_client.rs @@ -0,0 +1,36 @@ +use tokio::sync::mpsc::{channel, Sender}; + +use crate::component_server::ComponentRequestAndResponseSender; + +#[cfg(test)] +#[path = "component_server_client_test.rs"] +mod component_server_client_test; + +#[derive(Clone)] +pub struct ComponentClient +where + Request: Send + Sync, + Response: Send + Sync, +{ + tx: Sender>, +} + +impl ComponentClient +where + Request: Send + Sync, + Response: Send + Sync, +{ + pub fn new(tx: Sender>) -> Self { + Self { tx } + } + + // TODO(Tsabary, 1/5/2024): Consider implementation for messages without expected responses. + + pub async fn send(&self, request: Request) -> Response { + let (res_tx, mut res_rx) = channel::(1); + let request_and_res_tx = ComponentRequestAndResponseSender { request, tx: res_tx }; + self.tx.send(request_and_res_tx).await.expect("Outbound connection should be open."); + + res_rx.recv().await.expect("Inbound connection should be open.") + } +} diff --git a/crates/mempool_infra/src/component_client_rpc.rs b/crates/mempool_infra/src/component_client_rpc.rs new file mode 100644 index 00000000000..a9377fb5c1e --- /dev/null +++ b/crates/mempool_infra/src/component_client_rpc.rs @@ -0,0 +1,3 @@ +#[cfg(test)] +#[path = "component_server_client_rpc_test.rs"] +mod component_server_client_rpc_test; diff --git a/crates/mempool_infra/src/component_runner.rs b/crates/mempool_infra/src/component_runner.rs index e2403fb35d4..7458987a748 100644 --- a/crates/mempool_infra/src/component_runner.rs +++ b/crates/mempool_infra/src/component_runner.rs @@ -1,6 +1,9 @@ -use std::fmt::Debug; - use async_trait::async_trait; +use papyrus_config::dumping::SerializeConfig; + +#[cfg(test)] +#[path = "component_runner_test.rs"] +mod component_runner_test; #[derive(thiserror::Error, Debug, PartialEq)] pub enum ComponentStartError { @@ -10,6 +13,11 @@ pub enum ComponentStartError { InternalComponentError, } +/// Interface to create memepool components. +pub trait ComponentCreator { + fn create(config: T) -> Self; +} + /// Interface to start memepool components. #[async_trait] pub trait ComponentRunner { diff --git a/crates/mempool_infra/src/component_runner_test.rs b/crates/mempool_infra/src/component_runner_test.rs new file mode 100644 index 00000000000..ceedc8b5e10 --- /dev/null +++ b/crates/mempool_infra/src/component_runner_test.rs @@ -0,0 +1,129 @@ +use std::collections::BTreeMap; + +use assert_matches::assert_matches; +use async_trait::async_trait; +use papyrus_config::dumping::{ser_param, SerializeConfig}; +use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; +use pretty_assertions::assert_eq; +use serde::{Deserialize, Serialize}; + +use crate::component_runner::{ComponentCreator, ComponentRunner, ComponentStartError}; + +mod test_component_a { + use super::*; + + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] + pub struct TestConfigA { + pub bool_field: bool, + } + + impl SerializeConfig for TestConfigA { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ser_param( + "test1", + &self.bool_field, + "...", + ParamPrivacyInput::Public, + )]) + } + } + + #[derive(Debug)] + pub struct TestComponentA { + pub config: TestConfigA, + } + + impl TestComponentA { + async fn local_start(&self) -> Result<(), tokio::io::Error> { + println!("TestComponent1::local_start(), config: {:#?}", self.config); + Ok(()) + } + } + + impl ComponentCreator for TestComponentA { + fn create(config: TestConfigA) -> Self { + Self { config } + } + } + + #[async_trait] + impl ComponentRunner for TestComponentA { + async fn start(&self) -> Result<(), ComponentStartError> { + println!("TestComponent1::start(), component: {:#?}", self); + self.local_start().await.map_err(|_err| ComponentStartError::InternalComponentError) + } + } +} + +mod test_component_b { + use super::*; + + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] + pub struct TestConfigB { + pub u32_field: u32, + } + + impl SerializeConfig for TestConfigB { + fn dump(&self) -> BTreeMap { + BTreeMap::from_iter([ser_param( + "test2", + &self.u32_field, + "...", + ParamPrivacyInput::Public, + )]) + } + } + + #[derive(Debug)] + pub struct TestComponentB { + pub config: TestConfigB, + } + + impl ComponentCreator for TestComponentB { + fn create(config: TestConfigB) -> Self { + Self { config } + } + } + + #[async_trait] + impl ComponentRunner for TestComponentB { + async fn start(&self) -> Result<(), ComponentStartError> { + println!("TestComponent2::start(): component: {:#?}", self); + match self.config.u32_field { + 43 => Err(ComponentStartError::InternalComponentError), + 44 => Err(ComponentStartError::ComponentConfigError), + _ => Ok(()), + } + } + } +} + +use test_component_a::{TestComponentA, TestConfigA}; + +#[tokio::test] +async fn test_component_a() { + let test_config = TestConfigA { bool_field: true }; + let component = TestComponentA::create(test_config); + assert_matches!(component.start().await, Ok(())); +} + +use test_component_b::{TestComponentB, TestConfigB}; + +#[tokio::test] +async fn test_component_b() { + let test_config = TestConfigB { u32_field: 42 }; + let component = TestComponentB::create(test_config); + assert_matches!(component.start().await, Ok(())); + + let test_config = TestConfigB { u32_field: 43 }; + let component = TestComponentB::create(test_config); + assert_matches!(component.start().await, Err(e) => { + assert_eq!(e, ComponentStartError::InternalComponentError); + }); + + let test_config = TestConfigB { u32_field: 44 }; + let component = TestComponentB::create(test_config); + assert_matches!(component.start().await, Err(e) => { + assert_eq!(e, ComponentStartError::ComponentConfigError); + }); +} diff --git a/crates/mempool_infra/src/component_server.rs b/crates/mempool_infra/src/component_server.rs new file mode 100644 index 00000000000..fa7bc795fab --- /dev/null +++ b/crates/mempool_infra/src/component_server.rs @@ -0,0 +1,51 @@ +use async_trait::async_trait; +use tokio::sync::mpsc::{Receiver, Sender}; + +#[async_trait] +pub trait ComponentRequestHandler { + async fn handle_request(&mut self, request: Request) -> Response; +} + +pub struct ComponentRequestAndResponseSender +where + Request: Send + Sync, + Response: Send + Sync, +{ + pub request: Request, + pub tx: Sender, +} + +pub struct ComponentServer +where + Component: ComponentRequestHandler, + Request: Send + Sync, + Response: Send + Sync, +{ + component: Component, + rx: Receiver>, +} + +impl ComponentServer +where + Component: ComponentRequestHandler, + Request: Send + Sync, + Response: Send + Sync, +{ + pub fn new( + component: Component, + rx: Receiver>, + ) -> Self { + Self { component, rx } + } + + pub async fn start(&mut self) { + while let Some(request_and_res_tx) = self.rx.recv().await { + let request = request_and_res_tx.request; + let tx = request_and_res_tx.tx; + + let res = self.component.handle_request(request).await; + + tx.send(res).await.expect("Response connection should be open."); + } + } +} diff --git a/crates/mempool_infra/src/component_server_client_rpc_test.rs b/crates/mempool_infra/src/component_server_client_rpc_test.rs new file mode 100644 index 00000000000..c61a3ffb63c --- /dev/null +++ b/crates/mempool_infra/src/component_server_client_rpc_test.rs @@ -0,0 +1,7 @@ +pub mod component_a_service { + tonic::include_proto!("component_a_service"); +} + +pub mod component_b_service { + tonic::include_proto!("component_b_service"); +} diff --git a/crates/mempool_infra/src/component_server_client_test.rs b/crates/mempool_infra/src/component_server_client_test.rs new file mode 100644 index 00000000000..eefb6a6f5ed --- /dev/null +++ b/crates/mempool_infra/src/component_server_client_test.rs @@ -0,0 +1,173 @@ +use async_trait::async_trait; +use tokio::sync::mpsc::{channel, Sender}; +use tokio::task; + +use crate::component_client::ComponentClient; + +type ValueA = u32; +type ValueB = u8; + +use crate::component_server::{ + ComponentRequestAndResponseSender, + ComponentRequestHandler, + ComponentServer, +}; + +#[async_trait] +trait ComponentATrait: Send + Sync { + async fn a_get_value(&self) -> ValueA; +} + +#[async_trait] +trait ComponentBTrait: Send + Sync { + async fn b_get_value(&self) -> ValueB; +} + +struct ComponentA { + b: Box, +} + +#[async_trait] +impl ComponentATrait for ComponentA { + async fn a_get_value(&self) -> ValueA { + let b_value = self.b.b_get_value().await; + b_value.into() + } +} + +impl ComponentA { + fn new(b: Box) -> Self { + Self { b } + } +} + +// todo find which of these derives is needed +// todo add more messages +// todo send messages from b to a + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ComponentARequest { + AGetValue, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ComponentAResponse { + Value(ValueA), +} + +#[async_trait] +impl ComponentATrait for ComponentClient { + async fn a_get_value(&self) -> ValueA { + let res = self.send(ComponentARequest::AGetValue).await; + match res { + ComponentAResponse::Value(value) => value, + } + } +} + +#[async_trait] +impl ComponentRequestHandler for ComponentA { + async fn handle_request(&mut self, request: ComponentARequest) -> ComponentAResponse { + match request { + ComponentARequest::AGetValue => ComponentAResponse::Value(self.a_get_value().await), + } + } +} + +struct ComponentB { + value: ValueB, + _a: Box, +} + +#[async_trait] +impl ComponentBTrait for ComponentB { + async fn b_get_value(&self) -> ValueB { + self.value + } +} + +impl ComponentB { + fn new(value: ValueB, a: Box) -> Self { + Self { value, _a: a } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ComponentBRequest { + BGetValue, +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum ComponentBResponse { + Value(ValueB), +} + +#[async_trait] +impl ComponentBTrait for ComponentClient { + async fn b_get_value(&self) -> ValueB { + let res = self.send(ComponentBRequest::BGetValue).await; + match res { + ComponentBResponse::Value(value) => value, + } + } +} + +#[async_trait] +impl ComponentRequestHandler for ComponentB { + async fn handle_request(&mut self, request: ComponentBRequest) -> ComponentBResponse { + match request { + ComponentBRequest::BGetValue => ComponentBResponse::Value(self.b_get_value().await), + } + } +} + +async fn verify_response( + tx_a: Sender>, + expected_value: ValueA, +) { + let (tx_a_main, mut rx_a_main) = channel::(1); + + let request_and_res_tx: ComponentRequestAndResponseSender< + ComponentARequest, + ComponentAResponse, + > = ComponentRequestAndResponseSender { request: ComponentARequest::AGetValue, tx: tx_a_main }; + + tx_a.send(request_and_res_tx).await.unwrap(); + + let res = rx_a_main.recv().await.unwrap(); + match res { + ComponentAResponse::Value(value) => { + assert_eq!(value, expected_value); + } + } +} + +#[tokio::test] +async fn test_setup() { + let setup_value: ValueB = 30; + let expected_value: ValueA = setup_value.into(); + + let (tx_a, rx_a) = + channel::>(32); + let (tx_b, rx_b) = + channel::>(32); + + let a_client = ComponentClient::new(tx_a.clone()); + let b_client = ComponentClient::new(tx_b.clone()); + + let component_a = ComponentA::new(Box::new(b_client)); + let component_b = ComponentB::new(setup_value, Box::new(a_client)); + + let mut component_a_server = ComponentServer::new(component_a, rx_a); + let mut component_b_server = ComponentServer::new(component_b, rx_b); + + task::spawn(async move { + component_a_server.start().await; + }); + + task::spawn(async move { + component_b_server.start().await; + }); + + verify_response(tx_a.clone(), expected_value).await; +} diff --git a/crates/mempool_infra/src/lib.rs b/crates/mempool_infra/src/lib.rs index e41a1f3994a..7beabba7540 100644 --- a/crates/mempool_infra/src/lib.rs +++ b/crates/mempool_infra/src/lib.rs @@ -1,5 +1,5 @@ +pub mod component_client; +pub mod component_client_rpc; pub mod component_runner; +pub mod component_server; pub mod network_component; - -#[cfg(test)] -mod network_component_test; diff --git a/crates/mempool_infra/src/network_component.rs b/crates/mempool_infra/src/network_component.rs index 89ddae22012..3aef2a06e52 100644 --- a/crates/mempool_infra/src/network_component.rs +++ b/crates/mempool_infra/src/network_component.rs @@ -2,6 +2,10 @@ use async_trait::async_trait; use tokio::sync::mpsc::error::SendError; use tokio::sync::mpsc::{Receiver, Sender}; +#[cfg(test)] +#[path = "network_component_test.rs"] +mod network_component_test; + #[async_trait] pub trait CommunicationInterface { async fn send(&self, message: S) -> Result<(), SendError>; diff --git a/crates/mempool_node/Cargo.toml b/crates/mempool_node/Cargo.toml index 4516fb5be4f..308f205981a 100644 --- a/crates/mempool_node/Cargo.toml +++ b/crates/mempool_node/Cargo.toml @@ -9,19 +9,18 @@ license.workspace = true workspace = true [dependencies] -async-trait.workspace = true clap.workspace = true const_format.workspace = true starknet_gateway = { path = "../gateway", version = "0.4.0-dev.2" } serde.workspace = true -serde_json.workspace = true -# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. -starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", rev = "1b46b42" } -thiserror.workspace = true -papyrus_config.workspace = true +papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } tokio.workspace = true validator.workspace = true [dev-dependencies] +assert-json-diff.workspace = true assert_matches.workspace = true +colored.workspace = true pretty_assertions.workspace = true +serde_json.workspace = true +mempool_test_utils = {path = "../mempool_test_utils"} diff --git a/crates/mempool_node/src/bin/dump_config.rs b/crates/mempool_node/src/bin/dump_config.rs new file mode 100644 index 00000000000..f086fc58999 --- /dev/null +++ b/crates/mempool_node/src/bin/dump_config.rs @@ -0,0 +1,10 @@ +use mempool_node::config::{MempoolNodeConfig, DEFAULT_CONFIG_PATH}; +use papyrus_config::dumping::SerializeConfig; + +/// Updates the default config file by: +/// cargo run --bin dump_config -q +fn main() { + MempoolNodeConfig::default() + .dump_to_file(&vec![], DEFAULT_CONFIG_PATH) + .expect("dump to file error"); +} diff --git a/crates/mempool_node/src/config/config_test.rs b/crates/mempool_node/src/config/config_test.rs index 11b706d61cf..15c1f9b69e4 100644 --- a/crates/mempool_node/src/config/config_test.rs +++ b/crates/mempool_node/src/config/config_test.rs @@ -1,71 +1,79 @@ -#![allow(unused_imports)] -use std::env::{self, args}; +#[cfg(any(feature = "testing", test))] +use std::env::{self}; use std::fs::File; -use std::ops::IndexMut; -use std::path::{Path, PathBuf}; +use assert_json_diff::assert_json_eq; use assert_matches::assert_matches; +use colored::Colorize; +use mempool_test_utils::get_absolute_path; use papyrus_config::dumping::SerializeConfig; -use papyrus_config::loading::load_and_process_config; -use papyrus_config::presentation::get_config_presentation; -use papyrus_config::validators::ParsedValidationErrors; -use papyrus_config::{SerializationType, SerializedContent, SerializedParam}; +use papyrus_config::validators::{ParsedValidationError, ParsedValidationErrors}; use validator::Validate; use crate::config::{ - node_command, ComponentConfig, ComponentExecutionConfig, - GatewayConfig, MempoolNodeConfig, + DEFAULT_CONFIG_PATH, }; -const TEST_FILES_FOLDER: &str = "./src/test_files"; -const CONFIG_FILE: &str = "mempool_node_config.json"; - -fn get_config_file(file_name: &str) -> Result { - let config_file = File::open(Path::new(TEST_FILES_FOLDER).join(file_name)).unwrap(); - load_and_process_config::(config_file, node_command(), vec![]) -} - +/// Test the validation of the struct ComponentConfig. +/// The validation validates at least one of the components is set with execute: true. #[test] -fn test_valid_config() { - // Read the valid config file and validate its content. - let expected_config = MempoolNodeConfig { - components: ComponentConfig { - gateway_component: ComponentExecutionConfig { execute: true }, - mempool_component: ComponentExecutionConfig { execute: false }, - }, - gateway_config: GatewayConfig { ip: "0.0.0.0".parse().unwrap(), port: 8080 }, +fn test_components_config_validation() { + // Initialize an invalid config and check that the validator finds an error. + let mut component_config = ComponentConfig { + gateway_component: ComponentExecutionConfig { execute: false }, + mempool_component: ComponentExecutionConfig { execute: false }, }; - let loaded_config = get_config_file(CONFIG_FILE).unwrap(); - assert!(loaded_config.validate().is_ok()); - assert_eq!(loaded_config, expected_config); + assert_matches!(component_config.validate().unwrap_err(), validation_errors => { + let parsed_errors = ParsedValidationErrors::from(validation_errors); + assert_eq!(parsed_errors.0.len(), 1); + let parsed_validation_error = &parsed_errors.0[0]; + assert_matches!( + parsed_validation_error, + ParsedValidationError { param_path, code, message, params} + if ( + param_path == "__all__" && + code == "Invalid components configuration." && + params.is_empty() && + *message == Some("At least one component should be allowed to execute.".to_string()) + ) + ) + }); + + // Update the config to be valid and check that the validator finds no errors. + for (gateway_component_execute, mempool_component_execute) in + [(true, false), (false, true), (true, true)] + { + component_config.gateway_component.execute = gateway_component_execute; + component_config.mempool_component.execute = mempool_component_execute; + + assert_matches!(component_config.validate(), Ok(())); + } } +/// Test the validation of the struct MempoolNodeConfig and that the default config file is up to +/// date. To update the default config file, run: +/// cargo run --bin dump_config -q #[test] -fn test_components_config() { - // Read the valid config file and check that the validator finds no errors. - let mut config = get_config_file(CONFIG_FILE).unwrap(); - assert!(config.validate().is_ok()); +fn default_config_file_is_up_to_date() { + let default_config = MempoolNodeConfig::default(); + assert_matches!(default_config.validate(), Ok(())); + let from_code: serde_json::Value = serde_json::to_value(default_config.dump()).unwrap(); - // Invalidate the gateway component and check that the validator finds an error. - config.components.gateway_component.execute = false; - - assert_matches!(config.validate(), Err(e) => { - let parse_err = ParsedValidationErrors::from(e); - let mut error_msg = String::new(); - for error in parse_err.0 { - if error.param_path == "components.__all__" { - error_msg.push_str(&error.code); - break; - } - } - assert_eq!(error_msg, "Invalid components configuration."); - }); + env::set_current_dir(get_absolute_path("")).expect("Couldn't set working dir."); + let from_default_config_file: serde_json::Value = + serde_json::from_reader(File::open(DEFAULT_CONFIG_PATH).unwrap()).unwrap(); - // Validate the mempool component and check that the validator finds no errors. - config.components.mempool_component.execute = true; - assert!(config.validate().is_ok()); + println!( + "{}", + "Default config file doesn't match the default NodeConfig implementation. Please update \ + it using the dump_config binary." + .purple() + .bold() + ); + println!("Diffs shown below."); + assert_json_eq!(from_default_config_file, from_code) } diff --git a/crates/mempool_node/src/config/mod.rs b/crates/mempool_node/src/config/mod.rs index 460e8dab812..f7423b7c04d 100644 --- a/crates/mempool_node/src/config/mod.rs +++ b/crates/mempool_node/src/config/mod.rs @@ -10,7 +10,7 @@ use papyrus_config::dumping::{append_sub_config_name, ser_param, SerializeConfig use papyrus_config::loading::load_and_process_config; use papyrus_config::{ConfigError, ParamPath, ParamPrivacyInput, SerializedParam}; use serde::{Deserialize, Serialize}; -use starknet_gateway::config::GatewayConfig; +use starknet_gateway::config::{GatewayConfig, RpcStateReaderConfig}; use validator::{Validate, ValidationError}; use crate::version::VERSION_FULL; @@ -42,7 +42,7 @@ impl Default for ComponentExecutionConfig { } /// The components configuration. -#[derive(Clone, Debug, Serialize, Deserialize, Validate, PartialEq, Default)] +#[derive(Clone, Debug, Default, Serialize, Deserialize, Validate, PartialEq)] #[validate(schema(function = "validate_components_config"))] pub struct ComponentConfig { pub gateway_component: ComponentExecutionConfig, @@ -72,12 +72,14 @@ pub fn validate_components_config(components: &ComponentConfig) -> Result<(), Va } /// The configurations of the various components of the node. -#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Validate, Default)] +#[derive(Debug, Deserialize, Default, Serialize, Clone, PartialEq, Validate)] pub struct MempoolNodeConfig { #[validate] pub components: ComponentConfig, #[validate] pub gateway_config: GatewayConfig, + #[validate] + pub rpc_state_reader_config: RpcStateReaderConfig, } impl SerializeConfig for MempoolNodeConfig { @@ -86,6 +88,7 @@ impl SerializeConfig for MempoolNodeConfig { let mut sub_configs = vec![ append_sub_config_name(self.components.dump(), "components"), append_sub_config_name(self.gateway_config.dump(), "gateway_config"), + append_sub_config_name(self.rpc_state_reader_config.dump(), "rpc_state_reader_config"), ]; sub_configs.into_iter().flatten().collect() diff --git a/crates/mempool_node/src/test_files/mempool_node_config.json b/crates/mempool_node/src/test_files/mempool_node_config.json deleted file mode 100644 index 5c9c8e4d57d..00000000000 --- a/crates/mempool_node/src/test_files/mempool_node_config.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "components.gateway_component.execute": { - "description": "The component execution flag.", - "value": true, - "privacy": "Public" - }, - "components.mempool_component.execute": { - "description": "The component execution flag.", - "value": false, - "privacy": "Public" - }, - "gateway_config.ip": { - "description": "The gateway server ip.", - "value": "0.0.0.0", - "privacy": "Public" - }, - "gateway_config.port": { - "description": "The gateway server port.", - "value": 8080, - "privacy": "Public" - } -} - diff --git a/crates/mempool_test_utils/Cargo.toml b/crates/mempool_test_utils/Cargo.toml new file mode 100644 index 00000000000..b974abc2818 --- /dev/null +++ b/crates/mempool_test_utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mempool_test_utils" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[features] + +[dependencies] + + diff --git a/crates/mempool_test_utils/src/lib.rs b/crates/mempool_test_utils/src/lib.rs new file mode 100644 index 00000000000..374ea1201a1 --- /dev/null +++ b/crates/mempool_test_utils/src/lib.rs @@ -0,0 +1,7 @@ +use std::env; +use std::path::{Path, PathBuf}; + +/// Returns the absolute path from the project root. +pub fn get_absolute_path(relative_path: &str) -> PathBuf { + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("../..").join(relative_path) +} diff --git a/crates/mempool_types/Cargo.toml b/crates/mempool_types/Cargo.toml new file mode 100644 index 00000000000..9513cf4ac48 --- /dev/null +++ b/crates/mempool_types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "starknet_mempool_types" +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[lints] +workspace = true + +[dependencies] +async-trait.workspace = true +mempool_infra = { path = "../mempool_infra"} +# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. +starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "main-mempool" } +thiserror.workspace = true +tokio.workspace = true diff --git a/crates/mempool_types/src/errors.rs b/crates/mempool_types/src/errors.rs new file mode 100644 index 00000000000..bdc458b2b76 --- /dev/null +++ b/crates/mempool_types/src/errors.rs @@ -0,0 +1,8 @@ +use starknet_api::transaction::TransactionHash; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum MempoolError { + #[error("Duplicate transaction, of hash: {tx_hash}")] + DuplicateTransaction { tx_hash: TransactionHash }, +} diff --git a/crates/mempool_types/src/lib.rs b/crates/mempool_types/src/lib.rs new file mode 100644 index 00000000000..7e374b9e74c --- /dev/null +++ b/crates/mempool_types/src/lib.rs @@ -0,0 +1,3 @@ +pub mod errors; +pub mod mempool_types; +pub mod utils; diff --git a/crates/mempool_types/src/mempool_types.rs b/crates/mempool_types/src/mempool_types.rs new file mode 100644 index 00000000000..f9c4c2a1e89 --- /dev/null +++ b/crates/mempool_types/src/mempool_types.rs @@ -0,0 +1,129 @@ +use async_trait::async_trait; +use mempool_infra::component_client::ComponentClient; +use mempool_infra::network_component::NetworkComponent; +use starknet_api::core::{ContractAddress, Nonce}; +use starknet_api::transaction::{Tip, TransactionHash}; +use thiserror::Error; +use tokio::sync::mpsc::{Receiver, Sender}; + +use crate::errors::MempoolError; + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct ThinTransaction { + pub sender_address: ContractAddress, + pub tx_hash: TransactionHash, + pub tip: Tip, + pub nonce: Nonce, +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct AccountState { + pub nonce: Nonce, + // TODO: add balance field when needed. +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Account { + // TODO(Ayelet): Consider removing this field as it is duplicated in ThinTransaction. + pub address: ContractAddress, + pub state: AccountState, +} + +#[derive(Debug, Default)] +pub struct MempoolInput { + pub tx: ThinTransaction, + pub account: Account, +} + +#[derive(Debug)] +pub enum GatewayToMempoolMessage { + AddTransaction(MempoolInput), +} + +// TODO: Consider using `NetworkComponent` instead of defining the channels here. +// Currently, facing technical issues when using `NetworkComponent`. +pub struct BatcherToMempoolChannels { + pub rx: Receiver, + pub tx: Sender, +} + +pub enum BatcherToMempoolMessage { + GetTransactions(usize), +} +pub type MempoolToGatewayMessage = (); +pub type MempoolToBatcherMessage = Vec; + +pub type BatcherMempoolNetworkComponent = + NetworkComponent; +pub type MempoolBatcherNetworkComponent = + NetworkComponent; + +pub type GatewayNetworkComponent = + NetworkComponent; +pub type MempoolNetworkComponent = + NetworkComponent; + +pub type MempoolResult = Result; + +#[derive(Debug, Error)] +pub enum MempoolClientError { + #[error(transparent)] + MempoolError(#[from] MempoolError), + #[error(transparent)] + ClientError(#[from] ClientError), +} +pub type MempoolClientResult = Result; + +// TODO(Tsabary, 1/6/2024): Move communication-related definitions to a separate file. +#[derive(Debug, Error)] +pub enum ClientError { + #[error("Got an unexpected response type.")] + UnexpectedResponse, +} + +/// Serves as the mempool's shared interface. Requires `Send + Sync` to allow transferring and +/// sharing resources (inputs, futures) across threads. +#[async_trait] +pub trait MempoolClient: Send + Sync { + async fn add_tx(&self, mempool_input: MempoolInput) -> MempoolClientResult<()>; + async fn get_txs(&self, n_txs: usize) -> MempoolClientResult>; +} + +#[derive(Debug)] +pub enum MempoolRequest { + AddTransaction(MempoolInput), + GetTransactions(usize), +} + +#[derive(Debug)] +pub enum MempoolResponse { + AddTransaction(MempoolResult<()>), + GetTransactions(MempoolResult>), +} + +type MempoolClientImpl = ComponentClient; + +#[async_trait] +impl MempoolClient for MempoolClientImpl { + async fn add_tx(&self, mempool_input: MempoolInput) -> MempoolClientResult<()> { + let request = MempoolRequest::AddTransaction(mempool_input); + let res = self.send(request).await; + match res { + MempoolResponse::AddTransaction(Ok(res)) => Ok(res), + MempoolResponse::AddTransaction(Err(res)) => Err(MempoolClientError::MempoolError(res)), + _ => Err(MempoolClientError::ClientError(ClientError::UnexpectedResponse)), + } + } + + async fn get_txs(&self, n_txs: usize) -> MempoolClientResult> { + let request = MempoolRequest::GetTransactions(n_txs); + let res = self.send(request).await; + match res { + MempoolResponse::GetTransactions(Ok(res)) => Ok(res), + MempoolResponse::GetTransactions(Err(res)) => { + Err(MempoolClientError::MempoolError(res)) + } + _ => Err(MempoolClientError::ClientError(ClientError::UnexpectedResponse)), + } + } +} diff --git a/crates/mempool_types/src/utils.rs b/crates/mempool_types/src/utils.rs new file mode 100644 index 00000000000..c9b616b7ce6 --- /dev/null +++ b/crates/mempool_types/src/utils.rs @@ -0,0 +1,12 @@ +use starknet_api::core::ContractAddress; +use starknet_api::transaction::{Tip, TransactionHash}; + +use crate::mempool_types::ThinTransaction; + +pub fn create_thin_tx_for_testing( + tip: Tip, + tx_hash: TransactionHash, + sender_address: ContractAddress, +) -> ThinTransaction { + ThinTransaction { sender_address, tx_hash, tip, nonce: Default::default() } +} diff --git a/crates/native_blockifier/BUILD b/crates/native_blockifier/BUILD new file mode 100644 index 00000000000..1bdd707502c --- /dev/null +++ b/crates/native_blockifier/BUILD @@ -0,0 +1,26 @@ +load("@crates//:defs.bzl", "all_crate_deps") +load("@rules_rust//rust:defs.bzl", "rust_shared_library") +load("@rules_python//python:defs.bzl", "py_library") + +rust_shared_library( + name = "native_blockifier", + srcs = glob(["src/**/*.rs"]), + visibility = ["//crates:__subpackages__"], + crate_features=["testing"], + deps = all_crate_deps() + ["//crates/blockifier"], +) + +genrule( + name = "native_blockifier.pypy39-pp73-x86_64-linux-gnu.so", + srcs = ["native_blockifier"], + outs = ["native_blockifier/native_blockifier.pypy39-pp73-x86_64-linux-gnu.so"], + cmd = "cp $< $@", +) + +py_library( + name = "native_blockifier_py", + srcs = ["native_blockifier/__init__.py"], + data = ["native_blockifier.pypy39-pp73-x86_64-linux-gnu.so"], + imports = ["."], # Allow using "import native_blockifier" directly in any Python code. + visibility = ["//visibility:public"], +) diff --git a/crates/native_blockifier/Cargo.toml b/crates/native_blockifier/Cargo.toml new file mode 100644 index 00000000000..0297a08de68 --- /dev/null +++ b/crates/native_blockifier/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "native_blockifier" +version.workspace = true +edition.workspace = true +repository.workspace = true +license-file.workspace = true +description = "A Bridge between the Rust blockifier crate and Python." + +# Required for `cargo test` to work with Pyo3. +# On Python, make sure to compile this with the extension-module feature enabled. +# https://pyo3.rs/v0.19.1/faq#i-cant-run-cargo-test-or-i-cant-build-in-a-cargo-workspace-im-having-linker-issues-like-symbol-not-found-or-undefined-reference-to-_pyexc_systemerror +[features] +extension-module = ["pyo3/extension-module"] +testing = [] + +[lints] +workspace = true + +[lib] +name = "native_blockifier" +# "cdylib" is necessary to produce a shared library for Python to import from. +# +# Downstream Rust code (including code in `bin/`, `examples/`, and `tests/`) will not be able +# to `use string_sum;` unless the "rlib" or "lib" crate type is also included, e.g.: +# crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] + +[dependencies] +blockifier = { path = "../blockifier", features = ["testing"] } +cairo-lang-starknet-classes.workspace = true +cairo-lang-casm.workspace = true +cairo-vm.workspace = true +indexmap.workspace = true +log.workspace = true +num-bigint.workspace = true +papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } +pyo3 = { workspace = true, features = ["num-bigint", "hashbrown"] } +pyo3-log.workspace = true +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true, features = ["arbitrary_precision"] } +starknet_api = { version = "0.12.0-dev.0", features = ["testing"] } +thiserror.workspace = true + +[dev-dependencies] +cached.workspace = true +pretty_assertions.workspace = true +tempfile.workspace = true diff --git a/crates/native_blockifier/MANIFEST.in b/crates/native_blockifier/MANIFEST.in new file mode 100644 index 00000000000..30c2836807c --- /dev/null +++ b/crates/native_blockifier/MANIFEST.in @@ -0,0 +1,3 @@ +include Cargo.toml +recursive-include src * + diff --git a/crates/native_blockifier/README.md b/crates/native_blockifier/README.md new file mode 100644 index 00000000000..27b9f5cf8d1 --- /dev/null +++ b/crates/native_blockifier/README.md @@ -0,0 +1,5 @@ +# native_blockifier + +## Description + +A Bridge between the Rust blockifier crate and Python. diff --git a/crates/native_blockifier/native_blockifier/__init__.py b/crates/native_blockifier/native_blockifier/__init__.py new file mode 100644 index 00000000000..80a323e7e59 --- /dev/null +++ b/crates/native_blockifier/native_blockifier/__init__.py @@ -0,0 +1 @@ +from .native_blockifier import * diff --git a/crates/native_blockifier/setup.cfg b/crates/native_blockifier/setup.cfg new file mode 100644 index 00000000000..8a7ee93eff9 --- /dev/null +++ b/crates/native_blockifier/setup.cfg @@ -0,0 +1,6 @@ +[options] +packages = native_blockifier +zip_safe = False +setup_requires = setuptools-rust >= 0.12.1; +include_package_data = True + diff --git a/crates/native_blockifier/setup.py b/crates/native_blockifier/setup.py new file mode 100644 index 00000000000..1a3932801b1 --- /dev/null +++ b/crates/native_blockifier/setup.py @@ -0,0 +1,11 @@ +from distutils.core import setup +from setuptools_rust import Binding, RustExtension + +setup( + name="native_blockifier", + version="0.16.10", + rust_extensions=[RustExtension("native_blockifier.native_blockifier", binding=Binding.PyO3)], + author="Starkware", + author_email="info@starkware.co", + description="Rust binding for python", +) diff --git a/crates/native_blockifier/src/errors.rs b/crates/native_blockifier/src/errors.rs new file mode 100644 index 00000000000..c7a579ae839 --- /dev/null +++ b/crates/native_blockifier/src/errors.rs @@ -0,0 +1,113 @@ +use blockifier::blockifier::stateful_validator::StatefulValidatorError; +use blockifier::blockifier::transaction_executor::TransactionExecutorError; +use blockifier::execution::errors::ContractClassError; +use blockifier::state::errors::StateError; +use blockifier::transaction::errors::{ + ParseError, + TransactionExecutionError, + TransactionPreValidationError, +}; +use blockifier::transaction::transaction_types::TransactionType; +use cairo_vm::types::errors::program_errors::ProgramError; +use pyo3::create_exception; +use pyo3::exceptions::PyException; +use pyo3::prelude::*; +use starknet_api::StarknetApiError; +use thiserror::Error; + +pub type NativeBlockifierResult = Result; + +/// Defines `NativeBlockifierError` variants, their respective Python types, and implements a +/// conversion to `PyErr`. +macro_rules! native_blockifier_errors { + ($(($variant_name:ident, $from_error_type:ty, $py_error_name:ident)),*) => { + + #[derive(Debug, Error)] + pub enum NativeBlockifierError { + $( + #[error(transparent)] + $variant_name(#[from] $from_error_type) + ),* + } + + // Utility method for Python code to know which error types exist. + #[pyfunction] + pub fn py_error_names() -> Vec { + vec![$(String::from(stringify!($py_error_name))),*] + } + + // Creates new types that implement `Into`. + $(create_exception!(native_blockifier, $py_error_name, PyException);)* + + // Call to register all Python exceptions (and name list getter) in the native_blockifier + // module. + pub fn add_py_exceptions(py: Python<'_>, py_module: &PyModule) -> PyResult<()> { + $(py_module.add(stringify!($py_error_name), py.get_type::<$py_error_name>())?;)* + py_module.add_function(wrap_pyfunction!(py_error_names, py)?)?; + Ok(()) + } + + impl From for PyErr { + fn from(error: NativeBlockifierError) -> PyErr { + match error { + $(NativeBlockifierError::$variant_name(error) => $py_error_name::new_err( + // Constructs with the tuple `(error_code, error_message)`. + ( + String::from("native_blockifier.") + stringify!($py_error_name), + format!("{}", error), + ) + )),* + } + } + } + }; +} + +native_blockifier_errors!( + (ContractClassError, ContractClassError, PyContractClassError), + (NativeBlockifierInputError, NativeBlockifierInputError, PyNativeBlockifierInputError), + (ProgramError, ProgramError, PyProgramError), + (Pyo3Error, PyErr, PyPyo3Error), + (SerdeError, serde_json::Error, PySerdeError), + (StarknetApiError, StarknetApiError, PyStarknetApiError), + (StateError, StateError, PyStateError), + (StatefulValidatorError, StatefulValidatorError, PyStatefulValidatorError), + (StorageError, papyrus_storage::StorageError, PyStorageError), + (TransactionExecutionError, TransactionExecutionError, PyTransactionExecutionError), + (TransactionExecutorError, TransactionExecutorError, PyTransactionExecutorError), + (TransactionPreValidationError, TransactionPreValidationError, PyTransactionPreValidationError) +); + +#[derive(Debug, Error)] +pub enum NativeBlockifierInputError { + #[error("Max steps per tx out of range: {0}")] + MaxStepsPerTxOutOfRange(u32), + #[error("Max validate steps per tx out of range: {0}")] + MaxValidateStepsPerTxOutOfRange(u32), + #[error(transparent)] + InvalidNativeBlockifierInputError(#[from] InvalidNativeBlockifierInputError), + #[error(transparent)] + ParseError(#[from] ParseError), + #[error(transparent)] + ProgramError(#[from] ProgramError), + #[error(transparent)] + StarknetApiError(#[from] StarknetApiError), + #[error("Contract class of version {version} is unsupported.")] + UnsupportedContractClassVersion { version: usize }, + #[error("Transaction of type {tx_type:?} is unsupported in version {version}.")] + UnsupportedTransactionVersion { tx_type: TransactionType, version: usize }, +} + +#[derive(Debug, Error)] +pub enum InvalidNativeBlockifierInputError { + #[error("Invalid Wei gas price: {0}.")] + InvalidGasPriceWei(u128), + #[error("Invalid Fri gas price: {0}.")] + InvalidGasPriceFri(u128), + #[error("Invalid Wei data gas price: {0}.")] + InvalidDataGasPriceWei(u128), + #[error("Invalid Fri data gas price: {0}.")] + InvalidDataGasPriceFri(u128), +} + +create_exception!(native_blockifier, UndeclaredClassHashError, PyException); diff --git a/crates/native_blockifier/src/lib.rs b/crates/native_blockifier/src/lib.rs new file mode 100644 index 00000000000..ffd5ab12ddb --- /dev/null +++ b/crates/native_blockifier/src/lib.rs @@ -0,0 +1,79 @@ +// The blockifier crate supports only these specific architectures. +#![cfg(any( + target_pointer_width = "16", + target_pointer_width = "32", + target_pointer_width = "64", + target_pointer_width = "128" +))] + +pub mod errors; +pub mod py_block_executor; +pub mod py_declare; +pub mod py_deploy_account; +pub mod py_invoke_function; +pub mod py_l1_handler; +pub mod py_objects; +pub mod py_state_diff; +#[cfg(any(feature = "testing", test))] +pub mod py_test_utils; +// TODO(Dori, 1/4/2023): If and when supported in the Python build environment, use #[cfg(test)]. +pub mod py_testing_wrappers; +pub mod py_transaction; +pub mod py_utils; +pub mod py_validator; +pub mod state_readers; +pub mod storage; +pub mod test_utils; + +use errors::{add_py_exceptions, UndeclaredClassHashError}; +use py_block_executor::PyBlockExecutor; +use py_objects::PyExecutionResources; +use py_validator::PyValidator; +use pyo3::prelude::*; +use storage::StorageConfig; + +use crate::py_state_diff::PyStateDiff; +use crate::py_testing_wrappers::{ + estimate_casm_hash_computation_resources_for_testing_list, + estimate_casm_hash_computation_resources_for_testing_single, + raise_error_for_testing, +}; + +#[pymodule] +fn native_blockifier(py: Python<'_>, py_module: &PyModule) -> PyResult<()> { + // Initialize Rust-to-Python logging. + // Usage: just create a Python logger as usual, and it'll capture Rust prints. + pyo3_log::init(); + + py_module.add_class::()?; + py_module.add_class::()?; + py_module.add_class::()?; + py_module.add_class::()?; + py_module.add_class::()?; + py_module.add("UndeclaredClassHashError", py.get_type::())?; + add_py_exceptions(py, py_module)?; + + py_module.add_function(wrap_pyfunction!(blockifier_version, py)?)?; + + // TODO(Dori, 1/4/2023): If and when supported in the Python build environment, gate this code + // with #[cfg(test)]. + py_module.add_function(wrap_pyfunction!(raise_error_for_testing, py)?)?; + py_module.add_function(wrap_pyfunction!( + estimate_casm_hash_computation_resources_for_testing_list, + py + )?)?; + py_module.add_function(wrap_pyfunction!( + estimate_casm_hash_computation_resources_for_testing_single, + py + )?)?; + + Ok(()) +} + +/// Returns the version that the `blockifier` and `native_blockifier` crates were built with. +// Assumption: both `blockifier` and `native_blockifier` use `version.workspace` in the package +// section of their `Cargo.toml`. +#[pyfunction] +pub fn blockifier_version() -> PyResult { + Ok(env!("CARGO_PKG_VERSION").to_string()) +} diff --git a/crates/native_blockifier/src/py_block_executor.rs b/crates/native_blockifier/src/py_block_executor.rs new file mode 100644 index 00000000000..387cbd50849 --- /dev/null +++ b/crates/native_blockifier/src/py_block_executor.rs @@ -0,0 +1,578 @@ +use std::collections::HashMap; + +use blockifier::blockifier::block::{ + pre_process_block as pre_process_block_blockifier, + BlockInfo, + BlockNumberHashPair, + GasPrices, +}; +use blockifier::blockifier::config::TransactionExecutorConfig; +use blockifier::blockifier::transaction_executor::{TransactionExecutor, TransactionExecutorError}; +use blockifier::bouncer::BouncerConfig; +use blockifier::context::{BlockContext, ChainInfo, FeeTokenAddresses}; +use blockifier::execution::call_info::CallInfo; +use blockifier::state::cached_state::CachedState; +use blockifier::state::global_cache::GlobalContractCache; +use blockifier::state::state_api::State; +use blockifier::transaction::objects::{GasVector, ResourcesMapping, TransactionExecutionInfo}; +use blockifier::transaction::transaction_execution::Transaction; +use blockifier::versioned_constants::VersionedConstants; +use pyo3::prelude::*; +use pyo3::types::{PyBytes, PyList}; +use pyo3::{FromPyObject, PyAny, Python}; +use serde::Serialize; +use starknet_api::block::{BlockNumber, BlockTimestamp}; +use starknet_api::core::{ChainId, ContractAddress}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::Fee; + +use crate::errors::{ + InvalidNativeBlockifierInputError, + NativeBlockifierError, + NativeBlockifierInputError, + NativeBlockifierResult, +}; +use crate::py_objects::{PyBouncerConfig, PyConcurrencyConfig}; +use crate::py_state_diff::{PyBlockInfo, PyStateDiff}; +use crate::py_transaction::{get_py_tx_type, py_tx, PyClassInfo, PY_TX_PARSING_ERR}; +use crate::py_utils::{int_to_chain_id, PyFelt}; +use crate::state_readers::papyrus_state::PapyrusReader; +use crate::storage::{PapyrusStorage, Storage, StorageConfig}; + +pub(crate) type RawTransactionExecutionResult = Vec; +pub(crate) type PyVisitedSegmentsMapping = Vec<(PyFelt, Vec)>; + +#[cfg(test)] +#[path = "py_block_executor_test.rs"] +mod py_block_executor_test; + +const MAX_STEPS_PER_TX: u32 = 4_000_000; +const MAX_VALIDATE_STEPS_PER_TX: u32 = 1_000_000; +const RESULT_SERIALIZE_ERR: &str = "Failed serializing execution info."; + +/// Stripped down `TransactionExecutionInfo` for Python serialization, containing only the required +/// fields. +#[derive(Debug, Serialize)] +pub(crate) struct ThinTransactionExecutionInfo { + pub validate_call_info: Option, + pub execute_call_info: Option, + pub fee_transfer_call_info: Option, + pub actual_fee: Fee, + pub da_gas: GasVector, + pub actual_resources: ResourcesMapping, + pub revert_error: Option, +} + +impl ThinTransactionExecutionInfo { + pub fn from_tx_execution_info( + block_context: &BlockContext, + tx_execution_info: TransactionExecutionInfo, + ) -> Self { + Self { + validate_call_info: tx_execution_info.validate_call_info, + execute_call_info: tx_execution_info.execute_call_info, + fee_transfer_call_info: tx_execution_info.fee_transfer_call_info, + actual_fee: tx_execution_info.actual_fee, + da_gas: tx_execution_info.da_gas, + actual_resources: tx_execution_info.actual_resources.to_resources_mapping( + block_context.versioned_constants(), + block_context.block_info().use_kzg_da, + true, + ), + revert_error: tx_execution_info.revert_error, + } + } +} + +#[pyclass] +#[derive(Debug, Serialize)] +pub(crate) struct TypedTransactionExecutionInfo { + #[serde(flatten)] + pub info: ThinTransactionExecutionInfo, + pub tx_type: String, +} + +impl TypedTransactionExecutionInfo { + pub fn from_tx_execution_info( + block_context: &BlockContext, + tx_execution_info: TransactionExecutionInfo, + tx_type: String, + ) -> Self { + TypedTransactionExecutionInfo { + info: ThinTransactionExecutionInfo::from_tx_execution_info( + block_context, + tx_execution_info, + ), + tx_type, + } + } + + pub fn serialize(self) -> RawTransactionExecutionResult { + serde_json::to_vec(&self).expect(RESULT_SERIALIZE_ERR) + } +} + +#[pyclass] +pub struct PyBlockExecutor { + pub bouncer_config: BouncerConfig, + pub tx_executor_config: TransactionExecutorConfig, + pub general_config: PyGeneralConfig, + pub versioned_constants: VersionedConstants, + pub tx_executor: Option>, + /// `Send` trait is required for `pyclass` compatibility as Python objects must be threadsafe. + pub storage: Box, + pub global_contract_cache: GlobalContractCache, +} + +#[pymethods] +impl PyBlockExecutor { + #[new] + #[pyo3(signature = (bouncer_config, concurrency_config, general_config, validate_max_n_steps, max_recursion_depth, global_contract_cache_size, target_storage_config))] + pub fn create( + bouncer_config: PyBouncerConfig, + concurrency_config: PyConcurrencyConfig, + general_config: PyGeneralConfig, + validate_max_n_steps: u32, + max_recursion_depth: usize, + global_contract_cache_size: usize, + target_storage_config: StorageConfig, + ) -> Self { + log::debug!("Initializing Block Executor..."); + let storage = + PapyrusStorage::new(target_storage_config).expect("Failed to initialize storage"); + let versioned_constants = VersionedConstants::latest_constants_with_overrides( + validate_max_n_steps, + max_recursion_depth, + ); + log::debug!("Initialized Block Executor."); + + Self { + bouncer_config: bouncer_config.into(), + tx_executor_config: TransactionExecutorConfig { + concurrency_config: concurrency_config.into(), + }, + general_config, + versioned_constants, + tx_executor: None, + storage: Box::new(storage), + global_contract_cache: GlobalContractCache::new(global_contract_cache_size), + } + } + + // Transaction Execution API. + + /// Initializes the transaction executor for the given block. + #[pyo3(signature = (next_block_info, old_block_number_and_hash))] + fn setup_block_execution( + &mut self, + next_block_info: PyBlockInfo, + old_block_number_and_hash: Option<(u64, PyFelt)>, + ) -> NativeBlockifierResult<()> { + let papyrus_reader = self.get_aligned_reader(next_block_info.block_number); + let mut state = CachedState::new(papyrus_reader); + let block_context = pre_process_block( + &mut state, + old_block_number_and_hash, + &self.general_config, + &next_block_info, + &self.versioned_constants, + self.tx_executor_config.concurrency_config.enabled, + )?; + + let tx_executor = TransactionExecutor::new( + state, + block_context, + self.bouncer_config.clone(), + self.tx_executor_config.clone(), + ); + self.tx_executor = Some(tx_executor); + + Ok(()) + } + + fn teardown_block_execution(&mut self) { + self.tx_executor = None; + } + + #[pyo3(signature = (tx, optional_py_class_info))] + pub fn execute( + &mut self, + tx: &PyAny, + optional_py_class_info: Option, + ) -> NativeBlockifierResult> { + let charge_fee = true; + let tx_type: String = get_py_tx_type(tx).expect(PY_TX_PARSING_ERR).to_string(); + let tx: Transaction = py_tx(tx, optional_py_class_info).expect(PY_TX_PARSING_ERR); + let tx_execution_info = self.tx_executor().execute(&tx, charge_fee)?; + let typed_tx_execution_info = TypedTransactionExecutionInfo::from_tx_execution_info( + &self.tx_executor().block_context, + tx_execution_info, + tx_type, + ); + + // Serialize and convert to PyBytes. + let serialized_tx_execution_info = typed_tx_execution_info.serialize(); + Ok(Python::with_gil(|py| PyBytes::new(py, &serialized_tx_execution_info).into())) + } + + /// Executes the given transactions on the Blockifier state. + /// Stops if and when there is no more room in the block, and returns the executed transactions' + /// results as a PyList of (success (bool), serialized result (bytes)) tuples. + #[pyo3(signature = (txs_with_class_infos))] + pub fn execute_txs( + &mut self, + txs_with_class_infos: Vec<(&PyAny, Option)>, + ) -> Py { + let charge_fee = true; + + // Parse Py transactions. + let (tx_types, txs): (Vec, Vec) = txs_with_class_infos + .into_iter() + .map(|(tx, optional_py_class_info)| { + ( + get_py_tx_type(tx).expect(PY_TX_PARSING_ERR).to_string(), + py_tx(tx, optional_py_class_info).expect(PY_TX_PARSING_ERR), + ) + }) + .unzip(); + + // Run. + let results = self.tx_executor().execute_txs(&txs, charge_fee); + + // Process results. + // TODO(Yoni, 15/5/2024): serialize concurrently. + let block_context = &self.tx_executor().block_context; + let serialized_results: Vec<(bool, RawTransactionExecutionResult)> = results + .into_iter() + // Note: there might be less results than txs (if there is no room for all of them). + .zip(tx_types) + .map(|(result, tx_type)| match result { + Ok(tx_execution_info) => ( + true, + TypedTransactionExecutionInfo::from_tx_execution_info( + block_context, + tx_execution_info, + tx_type, + ) + .serialize(), + ), + Err(error) => (false, serialize_failure_reason(error)), + }) + .collect(); + + // Convert to Py types and allocate it on Python's heap, to be visible for Python's + // garbage collector. + Python::with_gil(|py| { + let py_serialized_results: Vec<(bool, Py)> = serialized_results + .into_iter() + .map(|(success, execution_result)| { + // Note that PyList converts the inner elements recursively, yet the default + // conversion of the execution result (Vec) is to a list of integers, which + // might be less efficient than bytes. + (success, PyBytes::new(py, &execution_result).into()) + }) + .collect(); + PyList::new(py, py_serialized_results).into() + }) + } + + /// Returns the state diff, a list of contract class hash with the corresponding list of + /// visited segment values and the block weights. + pub fn finalize( + &mut self, + ) -> NativeBlockifierResult<(PyStateDiff, PyVisitedSegmentsMapping, Py)> { + log::debug!("Finalizing execution..."); + let (commitment_state_diff, visited_pcs, block_weights) = self.tx_executor().finalize()?; + let visited_pcs = visited_pcs + .into_iter() + .map(|(class_hash, class_visited_pcs_vec)| { + (PyFelt::from(class_hash), class_visited_pcs_vec) + }) + .collect(); + let py_state_diff = PyStateDiff::from(commitment_state_diff); + + let serialized_block_weights = + serde_json::to_vec(&block_weights).expect("Failed serializing bouncer weights."); + let raw_block_weights = + Python::with_gil(|py| PyBytes::new(py, &serialized_block_weights).into()); + + log::debug!("Finalized execution."); + + Ok((py_state_diff, visited_pcs, raw_block_weights)) + } + + // Storage Alignment API. + + /// Appends state diff and block header into Papyrus storage. + // Previous block ID can either be a block hash (starting from a Papyrus snapshot), or a + // sequential ID (throughout sequencing). + #[pyo3(signature = ( + block_id, + previous_block_id, + py_block_info, + py_state_diff, + declared_class_hash_to_class, + deprecated_declared_class_hash_to_class + ))] + pub fn append_block( + &mut self, + block_id: u64, + previous_block_id: Option, + py_block_info: PyBlockInfo, + py_state_diff: PyStateDiff, + declared_class_hash_to_class: HashMap, + deprecated_declared_class_hash_to_class: HashMap, + ) -> NativeBlockifierResult<()> { + self.storage.append_block( + block_id, + previous_block_id, + py_block_info, + py_state_diff, + declared_class_hash_to_class, + deprecated_declared_class_hash_to_class, + ) + } + + /// Returns the next block number, for which block header was not yet appended. + /// Block header stream is usually ahead of the state diff stream, so this is the indicative + /// marker. + pub fn get_header_marker(&self) -> NativeBlockifierResult { + self.storage.get_header_marker() + } + + /// Returns the unique identifier of the given block number in bytes. + #[pyo3(signature = (block_number))] + fn get_block_id_at_target(&self, block_number: u64) -> NativeBlockifierResult> { + let optional_block_id_bytes = self.storage.get_block_id(block_number)?; + let Some(block_id_bytes) = optional_block_id_bytes else { return Ok(None) }; + + let mut block_id_fixed_bytes = [0_u8; 32]; + block_id_fixed_bytes.copy_from_slice(&block_id_bytes); + + Ok(Some(PyFelt(StarkFelt::new(block_id_fixed_bytes)?))) + } + + #[pyo3(signature = (source_block_number))] + pub fn validate_aligned(&self, source_block_number: u64) { + self.storage.validate_aligned(source_block_number); + } + + /// Atomically reverts block header and state diff of given block number. + /// If header exists without a state diff (usually the case), only the header is reverted. + /// (this is true for every partial existence of information at tables). + #[pyo3(signature = (block_number))] + pub fn revert_block(&mut self, block_number: u64) -> NativeBlockifierResult<()> { + // Clear global class cache, to peroperly revert classes declared in the reverted block. + self.global_contract_cache.clear(); + self.storage.revert_block(block_number) + } + + /// Deallocate the transaction executor and close storage connections. + pub fn close(&mut self) { + log::debug!("Closing Block Executor."); + // If the block was not finalized (due to some exception occuring _in Python_), we need + // to deallocate the transaction executor here to prevent leaks. + self.teardown_block_execution(); + self.storage.close(); + } + + #[cfg(any(feature = "testing", test))] + #[pyo3(signature = (concurrency_config, general_config, path, max_state_diff_size))] + #[staticmethod] + fn create_for_testing( + concurrency_config: PyConcurrencyConfig, + general_config: PyGeneralConfig, + path: std::path::PathBuf, + max_state_diff_size: usize, + ) -> Self { + use blockifier::bouncer::BouncerWeights; + use blockifier::state::global_cache::GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST; + Self { + bouncer_config: BouncerConfig { + block_max_capacity: BouncerWeights { + state_diff_size: max_state_diff_size, + ..BouncerWeights::max(false) + }, + block_max_capacity_with_keccak: BouncerWeights { + state_diff_size: max_state_diff_size, + ..BouncerWeights::max(true) + }, + }, + tx_executor_config: TransactionExecutorConfig { + concurrency_config: concurrency_config.into(), + }, + storage: Box::new(PapyrusStorage::new_for_testing( + path, + &general_config.starknet_os_config.chain_id, + )), + general_config, + versioned_constants: VersionedConstants::latest_constants().clone(), + tx_executor: None, + global_contract_cache: GlobalContractCache::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST), + } + } +} + +impl PyBlockExecutor { + pub fn tx_executor(&mut self) -> &mut TransactionExecutor { + self.tx_executor.as_mut().expect("Transaction executor should be initialized") + } + + fn get_aligned_reader(&self, next_block_number: u64) -> PapyrusReader { + // Full-node storage must be aligned to the Python storage before initializing a reader. + self.storage.validate_aligned(next_block_number); + PapyrusReader::new( + self.storage.reader().clone(), + BlockNumber(next_block_number), + self.global_contract_cache.clone(), + ) + } + + #[cfg(any(feature = "testing", test))] + pub fn create_for_testing_with_storage(storage: impl Storage + Send + 'static) -> Self { + use blockifier::state::global_cache::GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST; + Self { + bouncer_config: BouncerConfig::max(), + tx_executor_config: TransactionExecutorConfig::default(), + storage: Box::new(storage), + general_config: PyGeneralConfig::default(), + versioned_constants: VersionedConstants::latest_constants().clone(), + tx_executor: None, + global_contract_cache: GlobalContractCache::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST), + } + } +} + +#[derive(Default, FromPyObject)] +pub struct PyGeneralConfig { + pub starknet_os_config: PyOsConfig, + pub invoke_tx_max_n_steps: u32, + pub validate_max_n_steps: u32, +} + +#[derive(Clone, FromPyObject)] +pub struct PyOsConfig { + #[pyo3(from_py_with = "int_to_chain_id")] + pub chain_id: ChainId, + pub deprecated_fee_token_address: PyFelt, + pub fee_token_address: PyFelt, +} + +impl TryFrom for ChainInfo { + type Error = NativeBlockifierError; + + fn try_from(py_os_config: PyOsConfig) -> Result { + Ok(Self { + chain_id: py_os_config.chain_id, + fee_token_addresses: FeeTokenAddresses { + eth_fee_token_address: ContractAddress::try_from( + py_os_config.deprecated_fee_token_address.0, + )?, + strk_fee_token_address: ContractAddress::try_from( + py_os_config.fee_token_address.0, + )?, + }, + }) + } +} + +impl Default for PyOsConfig { + fn default() -> Self { + Self { + chain_id: ChainId("".to_string()), + deprecated_fee_token_address: Default::default(), + fee_token_address: Default::default(), + } + } +} + +pub fn into_block_context_args( + general_config: &PyGeneralConfig, + block_info: &PyBlockInfo, +) -> NativeBlockifierResult<(BlockInfo, ChainInfo)> { + let chain_info: ChainInfo = general_config.starknet_os_config.clone().try_into()?; + let block_info = BlockInfo { + block_number: BlockNumber(block_info.block_number), + block_timestamp: BlockTimestamp(block_info.block_timestamp), + sequencer_address: ContractAddress::try_from(block_info.sequencer_address.0)?, + gas_prices: GasPrices { + eth_l1_gas_price: block_info.l1_gas_price.price_in_wei.try_into().map_err(|_| { + NativeBlockifierInputError::InvalidNativeBlockifierInputError( + InvalidNativeBlockifierInputError::InvalidGasPriceWei( + block_info.l1_gas_price.price_in_wei, + ), + ) + })?, + strk_l1_gas_price: block_info.l1_gas_price.price_in_fri.try_into().map_err(|_| { + NativeBlockifierInputError::InvalidNativeBlockifierInputError( + InvalidNativeBlockifierInputError::InvalidGasPriceFri( + block_info.l1_gas_price.price_in_fri, + ), + ) + })?, + eth_l1_data_gas_price: block_info.l1_data_gas_price.price_in_wei.try_into().map_err( + |_| { + NativeBlockifierInputError::InvalidNativeBlockifierInputError( + InvalidNativeBlockifierInputError::InvalidDataGasPriceWei( + block_info.l1_data_gas_price.price_in_wei, + ), + ) + }, + )?, + strk_l1_data_gas_price: block_info.l1_data_gas_price.price_in_fri.try_into().map_err( + |_| { + NativeBlockifierInputError::InvalidNativeBlockifierInputError( + InvalidNativeBlockifierInputError::InvalidDataGasPriceFri( + block_info.l1_data_gas_price.price_in_fri, + ), + ) + }, + )?, + }, + use_kzg_da: block_info.use_kzg_da, + }; + + Ok((block_info, chain_info)) +} + +// Executes block pre-processing; see `blockifier::blockifier::block::pre_process_block` +// documentation. +fn pre_process_block( + state: &mut dyn State, + old_block_number_and_hash: Option<(u64, PyFelt)>, + general_config: &PyGeneralConfig, + block_info: &PyBlockInfo, + versioned_constants: &VersionedConstants, + concurrency_mode: bool, +) -> NativeBlockifierResult { + let old_block_number_and_hash = old_block_number_and_hash + .map(|(block_number, block_hash)| BlockNumberHashPair::new(block_number, block_hash.0)); + + // Input validation. + if versioned_constants.invoke_tx_max_n_steps > MAX_STEPS_PER_TX { + Err(NativeBlockifierInputError::MaxStepsPerTxOutOfRange( + versioned_constants.invoke_tx_max_n_steps, + ))?; + } else if versioned_constants.validate_max_n_steps > MAX_VALIDATE_STEPS_PER_TX { + Err(NativeBlockifierInputError::MaxValidateStepsPerTxOutOfRange( + versioned_constants.validate_max_n_steps, + ))?; + } + + let (block_info, chain_info) = into_block_context_args(general_config, block_info)?; + let block_context = pre_process_block_blockifier( + state, + old_block_number_and_hash, + block_info, + chain_info, + versioned_constants.clone(), + concurrency_mode, + )?; + + Ok(block_context) +} + +fn serialize_failure_reason(error: TransactionExecutorError) -> RawTransactionExecutionResult { + // TODO(Yoni, 1/7/2024): re-consider this serialization. + serde_json::to_vec(&format!("{}", error)).expect(RESULT_SERIALIZE_ERR) +} diff --git a/crates/native_blockifier/src/py_block_executor_test.rs b/crates/native_blockifier/src/py_block_executor_test.rs new file mode 100644 index 00000000000..7963168c9e7 --- /dev/null +++ b/crates/native_blockifier/src/py_block_executor_test.rs @@ -0,0 +1,80 @@ +use std::collections::HashMap; + +use blockifier::execution::contract_class::{ContractClass, ContractClassV1}; +use blockifier::state::state_api::StateReader; +use cached::Cached; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use pretty_assertions::assert_eq; +use starknet_api::class_hash; +use starknet_api::core::ClassHash; +use starknet_api::hash::{StarkFelt, StarkHash}; + +use crate::py_block_executor::{PyBlockExecutor, PyGeneralConfig}; +use crate::py_objects::PyConcurrencyConfig; +use crate::py_state_diff::{PyBlockInfo, PyStateDiff}; +use crate::py_utils::PyFelt; +use crate::test_utils::MockStorage; + +#[test] +fn global_contract_cache_update() { + // Initialize executor and set a contract class on the state. + let casm = CasmContractClass::default(); + let contract_class = ContractClass::V1(ContractClassV1::try_from(casm.clone()).unwrap()); + let class_hash = class_hash!("0x1"); + + let temp_storage_path = tempfile::tempdir().unwrap().into_path(); + let mut block_executor = PyBlockExecutor::create_for_testing( + PyConcurrencyConfig::default(), + PyGeneralConfig::default(), + temp_storage_path, + 4000, + ); + block_executor + .append_block( + 0, + None, + PyBlockInfo::default(), + PyStateDiff::default(), + HashMap::from([( + class_hash.into(), + (PyFelt::from(1_u8), serde_json::to_string(&casm).unwrap()), + )]), + HashMap::default(), + ) + .unwrap(); + + let sentinel_block_number_and_hash = None; // Information does not exist for block 0. + block_executor + .setup_block_execution( + PyBlockInfo { block_number: 1, ..PyBlockInfo::default() }, + sentinel_block_number_and_hash, + ) + .unwrap(); + + assert_eq!(block_executor.global_contract_cache.lock().cache_size(), 0); + + let queried_contract_class = + block_executor.tx_executor().state.get_compiled_contract_class(class_hash).unwrap(); + + assert_eq!(queried_contract_class, contract_class); + assert_eq!(block_executor.global_contract_cache.lock().cache_size(), 1); +} + +#[test] +fn get_block_id() { + let max_class_hash = [ + 0x9, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, 0xf, + ]; + let max_class_hash_vec = Vec::from(max_class_hash); + let expected_max_class_hash_as_py_felt = PyFelt(StarkFelt::new(max_class_hash).unwrap()); + + let storage = + MockStorage { block_number_to_class_hash: HashMap::from([(1138, max_class_hash_vec)]) }; + let block_executor = PyBlockExecutor::create_for_testing_with_storage(storage); + + assert_eq!( + block_executor.get_block_id_at_target(1138).unwrap().unwrap(), + expected_max_class_hash_as_py_felt + ); +} diff --git a/crates/native_blockifier/src/py_declare.rs b/crates/native_blockifier/src/py_declare.rs new file mode 100644 index 00000000000..fd4feed3dbc --- /dev/null +++ b/crates/native_blockifier/src/py_declare.rs @@ -0,0 +1,143 @@ +use std::convert::TryFrom; + +use blockifier::transaction::transaction_types::TransactionType; +use blockifier::transaction::transactions::DeclareTransaction; +use pyo3::prelude::*; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::transaction::{ + AccountDeploymentData, + DeclareTransactionV0V1, + DeclareTransactionV2, + DeclareTransactionV3, + Fee, + PaymasterData, + ResourceBoundsMapping, + Tip, + TransactionHash, + TransactionSignature, +}; + +use crate::errors::{NativeBlockifierInputError, NativeBlockifierResult}; +use crate::py_transaction::{PyClassInfo, PyDataAvailabilityMode, PyResourceBoundsMapping}; +use crate::py_utils::{from_py_felts, py_attr, PyFelt}; + +#[derive(FromPyObject)] +struct PyDeclareTransactionV0V1 { + pub max_fee: u128, + pub signature: Vec, + pub nonce: PyFelt, + pub class_hash: PyFelt, + pub sender_address: PyFelt, +} + +impl TryFrom for DeclareTransactionV0V1 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyDeclareTransactionV0V1) -> Result { + Ok(Self { + max_fee: Fee(tx.max_fee), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + class_hash: ClassHash(tx.class_hash.0), + sender_address: ContractAddress::try_from(tx.sender_address.0)?, + }) + } +} + +#[derive(FromPyObject)] +struct PyDeclareTransactionV2 { + pub max_fee: u128, + pub signature: Vec, + pub nonce: PyFelt, + pub class_hash: PyFelt, + pub compiled_class_hash: PyFelt, + pub sender_address: PyFelt, +} + +impl TryFrom for DeclareTransactionV2 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyDeclareTransactionV2) -> Result { + Ok(Self { + max_fee: Fee(tx.max_fee), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + class_hash: ClassHash(tx.class_hash.0), + compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.0), + sender_address: ContractAddress::try_from(tx.sender_address.0)?, + }) + } +} + +#[derive(FromPyObject)] +struct PyDeclareTransactionV3 { + pub resource_bounds: PyResourceBoundsMapping, + pub tip: u64, + pub signature: Vec, + pub nonce: PyFelt, + pub class_hash: PyFelt, + pub compiled_class_hash: PyFelt, + pub sender_address: PyFelt, + pub nonce_data_availability_mode: PyDataAvailabilityMode, + pub fee_data_availability_mode: PyDataAvailabilityMode, + pub paymaster_data: Vec, + pub account_deployment_data: Vec, +} + +impl TryFrom for DeclareTransactionV3 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyDeclareTransactionV3) -> Result { + Ok(Self { + resource_bounds: ResourceBoundsMapping::try_from(tx.resource_bounds)?, + tip: Tip(tx.tip), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + class_hash: ClassHash(tx.class_hash.0), + compiled_class_hash: CompiledClassHash(tx.compiled_class_hash.0), + sender_address: ContractAddress::try_from(tx.sender_address.0)?, + nonce_data_availability_mode: DataAvailabilityMode::from( + tx.nonce_data_availability_mode, + ), + fee_data_availability_mode: DataAvailabilityMode::from(tx.fee_data_availability_mode), + paymaster_data: PaymasterData(from_py_felts(tx.paymaster_data)), + account_deployment_data: AccountDeploymentData(from_py_felts( + tx.account_deployment_data, + )), + }) + } +} + +pub fn py_declare( + py_tx: &PyAny, + py_class_info: PyClassInfo, +) -> NativeBlockifierResult { + let version = usize::try_from(py_attr::(py_tx, "version")?.0)?; + let tx = match version { + 0 => { + let py_declare_tx: PyDeclareTransactionV0V1 = py_tx.extract()?; + let declare_tx = DeclareTransactionV0V1::try_from(py_declare_tx)?; + Ok(starknet_api::transaction::DeclareTransaction::V0(declare_tx)) + } + 1 => { + let py_declare_tx: PyDeclareTransactionV0V1 = py_tx.extract()?; + let declare_tx = DeclareTransactionV0V1::try_from(py_declare_tx)?; + Ok(starknet_api::transaction::DeclareTransaction::V1(declare_tx)) + } + 2 => { + let py_declare_tx: PyDeclareTransactionV2 = py_tx.extract()?; + let declare_tx = DeclareTransactionV2::try_from(py_declare_tx)?; + Ok(starknet_api::transaction::DeclareTransaction::V2(declare_tx)) + } + 3 => { + let py_declare_tx: PyDeclareTransactionV3 = py_tx.extract()?; + let declare_tx = DeclareTransactionV3::try_from(py_declare_tx)?; + Ok(starknet_api::transaction::DeclareTransaction::V3(declare_tx)) + } + _ => Err(NativeBlockifierInputError::UnsupportedTransactionVersion { + tx_type: TransactionType::Declare, + version, + }), + }?; + let tx_hash = TransactionHash(py_attr::(py_tx, "hash_value")?.0); + let class_info = PyClassInfo::try_from(py_class_info, &tx)?; + Ok(DeclareTransaction::new(tx, tx_hash, class_info)?) +} diff --git a/crates/native_blockifier/src/py_deploy_account.rs b/crates/native_blockifier/src/py_deploy_account.rs new file mode 100644 index 00000000000..9d12a4f1c14 --- /dev/null +++ b/crates/native_blockifier/src/py_deploy_account.rs @@ -0,0 +1,105 @@ +use std::sync::Arc; + +use blockifier::transaction::transaction_types::TransactionType; +use blockifier::transaction::transactions::DeployAccountTransaction; +use pyo3::prelude::*; +use starknet_api::core::{ClassHash, ContractAddress, Nonce}; +use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::transaction::{ + Calldata, + ContractAddressSalt, + DeployAccountTransactionV1, + DeployAccountTransactionV3, + Fee, + PaymasterData, + ResourceBoundsMapping, + Tip, + TransactionHash, + TransactionSignature, +}; + +use crate::errors::{NativeBlockifierInputError, NativeBlockifierResult}; +use crate::py_transaction::{PyDataAvailabilityMode, PyResourceBoundsMapping}; +use crate::py_utils::{from_py_felts, py_attr, PyFelt}; + +#[derive(FromPyObject)] +struct PyDeployAccountTransactionV1 { + pub max_fee: u128, + pub signature: Vec, + pub nonce: PyFelt, + pub class_hash: PyFelt, + pub contract_address_salt: PyFelt, + pub constructor_calldata: Vec, +} + +impl From for DeployAccountTransactionV1 { + fn from(tx: PyDeployAccountTransactionV1) -> Self { + Self { + max_fee: Fee(tx.max_fee), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + class_hash: ClassHash(tx.class_hash.0), + contract_address_salt: ContractAddressSalt(tx.contract_address_salt.0), + constructor_calldata: Calldata(Arc::from(from_py_felts(tx.constructor_calldata))), + } + } +} + +#[derive(FromPyObject)] +struct PyDeployAccountTransactionV3 { + pub resource_bounds: PyResourceBoundsMapping, + pub tip: u64, + pub signature: Vec, + pub nonce: PyFelt, + pub class_hash: PyFelt, + pub contract_address_salt: PyFelt, + pub constructor_calldata: Vec, + pub nonce_data_availability_mode: PyDataAvailabilityMode, + pub fee_data_availability_mode: PyDataAvailabilityMode, + pub paymaster_data: Vec, +} + +impl TryFrom for DeployAccountTransactionV3 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyDeployAccountTransactionV3) -> Result { + Ok(Self { + resource_bounds: ResourceBoundsMapping::try_from(tx.resource_bounds)?, + tip: Tip(tx.tip), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + class_hash: ClassHash(tx.class_hash.0), + contract_address_salt: ContractAddressSalt(tx.contract_address_salt.0), + constructor_calldata: Calldata(Arc::from(from_py_felts(tx.constructor_calldata))), + nonce_data_availability_mode: DataAvailabilityMode::from( + tx.nonce_data_availability_mode, + ), + fee_data_availability_mode: DataAvailabilityMode::from(tx.fee_data_availability_mode), + paymaster_data: PaymasterData(from_py_felts(tx.paymaster_data)), + }) + } +} + +pub fn py_deploy_account(py_tx: &PyAny) -> NativeBlockifierResult { + let version = usize::try_from(py_attr::(py_tx, "version")?.0)?; + let tx = match version { + 1 => { + let py_deploy_account_tx: PyDeployAccountTransactionV1 = py_tx.extract()?; + let deploy_account_tx = DeployAccountTransactionV1::from(py_deploy_account_tx); + Ok(starknet_api::transaction::DeployAccountTransaction::V1(deploy_account_tx)) + } + 3 => { + let py_deploy_account_tx: PyDeployAccountTransactionV3 = py_tx.extract()?; + let deploy_account_tx = DeployAccountTransactionV3::try_from(py_deploy_account_tx)?; + Ok(starknet_api::transaction::DeployAccountTransaction::V3(deploy_account_tx)) + } + _ => Err(NativeBlockifierInputError::UnsupportedTransactionVersion { + tx_type: TransactionType::DeployAccount, + version, + }), + }?; + + let tx_hash = TransactionHash(py_attr::(py_tx, "hash_value")?.0); + let contract_address = + ContractAddress::try_from(py_attr::(py_tx, "sender_address")?.0)?; + Ok(DeployAccountTransaction::new(tx, tx_hash, contract_address)) +} diff --git a/crates/native_blockifier/src/py_invoke_function.rs b/crates/native_blockifier/src/py_invoke_function.rs new file mode 100644 index 00000000000..d28a80a3f55 --- /dev/null +++ b/crates/native_blockifier/src/py_invoke_function.rs @@ -0,0 +1,133 @@ +use std::convert::TryFrom; +use std::sync::Arc; + +use blockifier::transaction::transaction_types::TransactionType; +use blockifier::transaction::transactions::InvokeTransaction; +use pyo3::prelude::*; +use starknet_api::core::{ContractAddress, EntryPointSelector, Nonce}; +use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::transaction::{ + AccountDeploymentData, + Calldata, + Fee, + InvokeTransactionV0, + InvokeTransactionV1, + InvokeTransactionV3, + PaymasterData, + ResourceBoundsMapping, + Tip, + TransactionHash, + TransactionSignature, +}; + +use crate::errors::{NativeBlockifierInputError, NativeBlockifierResult}; +use crate::py_transaction::{PyDataAvailabilityMode, PyResourceBoundsMapping}; +use crate::py_utils::{from_py_felts, py_attr, PyFelt}; + +#[derive(FromPyObject)] +struct PyInvokeTransactionV0 { + pub max_fee: u128, + pub signature: Vec, + pub sender_address: PyFelt, + pub entry_point_selector: PyFelt, + pub calldata: Vec, +} + +impl TryFrom for InvokeTransactionV0 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyInvokeTransactionV0) -> Result { + Ok(Self { + max_fee: Fee(tx.max_fee), + signature: TransactionSignature(from_py_felts(tx.signature)), + contract_address: ContractAddress::try_from(tx.sender_address.0)?, + entry_point_selector: EntryPointSelector(tx.entry_point_selector.0), + calldata: Calldata(Arc::from(from_py_felts(tx.calldata))), + }) + } +} + +#[derive(FromPyObject)] +struct PyInvokeTransactionV1 { + pub max_fee: u128, + pub signature: Vec, + pub nonce: PyFelt, + pub sender_address: PyFelt, + pub calldata: Vec, +} + +impl TryFrom for InvokeTransactionV1 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyInvokeTransactionV1) -> Result { + Ok(Self { + max_fee: Fee(tx.max_fee), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + sender_address: ContractAddress::try_from(tx.sender_address.0)?, + calldata: Calldata(Arc::from(from_py_felts(tx.calldata))), + }) + } +} + +#[derive(FromPyObject)] +struct PyInvokeTransactionV3 { + pub resource_bounds: PyResourceBoundsMapping, + pub tip: u64, + pub signature: Vec, + pub nonce: PyFelt, + pub sender_address: PyFelt, + pub calldata: Vec, + pub nonce_data_availability_mode: PyDataAvailabilityMode, + pub fee_data_availability_mode: PyDataAvailabilityMode, + pub paymaster_data: Vec, + pub account_deployment_data: Vec, +} + +impl TryFrom for InvokeTransactionV3 { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyInvokeTransactionV3) -> Result { + Ok(Self { + resource_bounds: ResourceBoundsMapping::try_from(tx.resource_bounds)?, + tip: Tip(tx.tip), + signature: TransactionSignature(from_py_felts(tx.signature)), + nonce: Nonce(tx.nonce.0), + sender_address: ContractAddress::try_from(tx.sender_address.0)?, + calldata: Calldata(Arc::from(from_py_felts(tx.calldata))), + nonce_data_availability_mode: DataAvailabilityMode::from( + tx.nonce_data_availability_mode, + ), + fee_data_availability_mode: DataAvailabilityMode::from(tx.fee_data_availability_mode), + paymaster_data: PaymasterData(from_py_felts(tx.paymaster_data)), + account_deployment_data: AccountDeploymentData(from_py_felts( + tx.account_deployment_data, + )), + }) + } +} + +pub fn py_invoke_function(py_tx: &PyAny) -> NativeBlockifierResult { + let version = usize::try_from(py_attr::(py_tx, "version")?.0)?; + let tx = match version { + 0 => { + let py_invoke_tx: PyInvokeTransactionV0 = py_tx.extract()?; + let invoke_tx = InvokeTransactionV0::try_from(py_invoke_tx)?; + Ok(starknet_api::transaction::InvokeTransaction::V0(invoke_tx)) + } + 1 => { + let py_invoke_tx: PyInvokeTransactionV1 = py_tx.extract()?; + let invoke_tx = InvokeTransactionV1::try_from(py_invoke_tx)?; + Ok(starknet_api::transaction::InvokeTransaction::V1(invoke_tx)) + } + 3 => { + let py_invoke_tx: PyInvokeTransactionV3 = py_tx.extract()?; + let invoke_tx = InvokeTransactionV3::try_from(py_invoke_tx)?; + Ok(starknet_api::transaction::InvokeTransaction::V3(invoke_tx)) + } + _ => Err(NativeBlockifierInputError::UnsupportedTransactionVersion { + tx_type: TransactionType::InvokeFunction, + version, + }), + }?; + + let tx_hash = TransactionHash(py_attr::(py_tx, "hash_value")?.0); + Ok(InvokeTransaction::new(tx, tx_hash)) +} diff --git a/crates/native_blockifier/src/py_l1_handler.rs b/crates/native_blockifier/src/py_l1_handler.rs new file mode 100644 index 00000000000..3c72eb75088 --- /dev/null +++ b/crates/native_blockifier/src/py_l1_handler.rs @@ -0,0 +1,40 @@ +use std::sync::Arc; + +use blockifier::abi::constants; +use blockifier::transaction::transactions::L1HandlerTransaction; +use pyo3::prelude::*; +use starknet_api::core::{ContractAddress, EntryPointSelector, Nonce}; +use starknet_api::transaction::{Calldata, Fee, TransactionHash}; + +use crate::errors::{NativeBlockifierInputError, NativeBlockifierResult}; +use crate::py_utils::{from_py_felts, py_attr, PyFelt}; + +#[derive(FromPyObject)] +struct PyL1HandlerTransaction { + pub nonce: PyFelt, + pub contract_address: PyFelt, + pub entry_point_selector: PyFelt, + pub calldata: Vec, +} + +impl TryFrom for starknet_api::transaction::L1HandlerTransaction { + type Error = NativeBlockifierInputError; + fn try_from(tx: PyL1HandlerTransaction) -> Result { + Ok(Self { + version: constants::L1_HANDLER_VERSION, + nonce: Nonce(tx.nonce.0), + contract_address: ContractAddress::try_from(tx.contract_address.0)?, + entry_point_selector: EntryPointSelector(tx.entry_point_selector.0), + calldata: Calldata(Arc::from(from_py_felts(tx.calldata))), + }) + } +} + +pub fn py_l1_handler(py_tx: &PyAny) -> NativeBlockifierResult { + let py_l1_handler_tx: PyL1HandlerTransaction = py_tx.extract()?; + let tx = starknet_api::transaction::L1HandlerTransaction::try_from(py_l1_handler_tx)?; + let tx_hash = TransactionHash(py_attr::(py_tx, "hash_value")?.0); + let paid_fee_on_l1 = Fee(py_attr::(py_tx, "paid_fee_on_l1")?); + + Ok(L1HandlerTransaction { tx, tx_hash, paid_fee_on_l1 }) +} diff --git a/crates/native_blockifier/src/py_objects.rs b/crates/native_blockifier/src/py_objects.rs new file mode 100644 index 00000000000..ac5c2da7896 --- /dev/null +++ b/crates/native_blockifier/src/py_objects.rs @@ -0,0 +1,83 @@ +use std::collections::HashMap; + +use blockifier::abi::constants; +use blockifier::blockifier::config::ConcurrencyConfig; +use blockifier::bouncer::{BouncerConfig, BouncerWeights, BuiltinCount}; +use cairo_vm::vm::runners::cairo_runner::ExecutionResources; +use pyo3::prelude::*; + +// From Rust to Python. + +#[pyclass] +#[derive(Clone, Default)] +pub struct PyExecutionResources { + #[pyo3(get)] + pub n_steps: usize, + #[pyo3(get)] + pub builtin_instance_counter: HashMap, + #[pyo3(get)] + pub n_memory_holes: usize, +} + +impl From for PyExecutionResources { + fn from(resources: ExecutionResources) -> Self { + Self { + n_steps: resources.n_steps, + builtin_instance_counter: resources.builtin_instance_counter, + n_memory_holes: resources.n_memory_holes, + } + } +} + +// From Python to Rust. + +#[derive(Clone, Debug, FromPyObject)] +pub struct PyBouncerConfig { + pub full_total_weights_with_keccak: HashMap, + pub full_total_weights: HashMap, +} + +impl From for BouncerConfig { + fn from(py_bouncer_config: PyBouncerConfig) -> Self { + BouncerConfig { + block_max_capacity: hash_map_into_bouncer_weights( + py_bouncer_config.full_total_weights.clone(), + ), + block_max_capacity_with_keccak: hash_map_into_bouncer_weights( + py_bouncer_config.full_total_weights_with_keccak.clone(), + ), + } + } +} + +fn hash_map_into_bouncer_weights(mut data: HashMap) -> BouncerWeights { + BouncerWeights { + gas: data.remove(constants::L1_GAS_USAGE).expect("gas_weight must be present"), + n_steps: data.remove(constants::N_STEPS_RESOURCE).expect("n_steps must be present"), + message_segment_length: data + .remove(constants::MESSAGE_SEGMENT_LENGTH) + .expect("message_segment_length must be present"), + state_diff_size: data + .remove(constants::STATE_DIFF_SIZE) + .expect("state_diff_size must be present"), + n_events: data.remove(constants::N_EVENTS).expect("n_events must be present"), + builtin_count: BuiltinCount::from(data), + } +} + +#[derive(Debug, Default, FromPyObject)] +pub struct PyConcurrencyConfig { + pub enabled: bool, + pub n_workers: usize, + pub chunk_size: usize, +} + +impl From for ConcurrencyConfig { + fn from(py_concurrency_config: PyConcurrencyConfig) -> Self { + ConcurrencyConfig { + enabled: py_concurrency_config.enabled, + n_workers: py_concurrency_config.n_workers, + chunk_size: py_concurrency_config.chunk_size, + } + } +} diff --git a/crates/native_blockifier/src/py_state_diff.rs b/crates/native_blockifier/src/py_state_diff.rs new file mode 100644 index 00000000000..a857c66821b --- /dev/null +++ b/crates/native_blockifier/src/py_state_diff.rs @@ -0,0 +1,157 @@ +use std::collections::HashMap; +use std::convert::TryFrom; + +use blockifier::state::cached_state::CommitmentStateDiff; +use blockifier::test_utils::{ + DEFAULT_ETH_L1_DATA_GAS_PRICE, + DEFAULT_ETH_L1_GAS_PRICE, + DEFAULT_STRK_L1_DATA_GAS_PRICE, + DEFAULT_STRK_L1_GAS_PRICE, +}; +use indexmap::IndexMap; +use pyo3::prelude::*; +use starknet_api::core::{ClassHash, ContractAddress, Nonce}; +use starknet_api::state::{StateDiff, StorageKey}; + +use crate::errors::{NativeBlockifierError, NativeBlockifierResult}; +use crate::py_utils::PyFelt; + +#[pyclass] +#[derive(Default, FromPyObject)] +// TODO: Add support for returning the `declared_classes` to python. +pub struct PyStateDiff { + #[pyo3(get)] + pub address_to_class_hash: HashMap, + #[pyo3(get)] + pub address_to_nonce: HashMap, + #[pyo3(get)] + pub storage_updates: HashMap>, + #[pyo3(get)] + pub class_hash_to_compiled_class_hash: HashMap, +} + +impl TryFrom for StateDiff { + type Error = NativeBlockifierError; + + fn try_from(state_diff: PyStateDiff) -> NativeBlockifierResult { + let mut deployed_contracts: IndexMap = IndexMap::new(); + for (address, class_hash) in state_diff.address_to_class_hash { + let address = ContractAddress::try_from(address.0)?; + let class_hash = ClassHash(class_hash.0); + deployed_contracts.insert(address, class_hash); + } + + let mut storage_diffs = IndexMap::new(); + for (address, storage_mapping) in state_diff.storage_updates { + let address = ContractAddress::try_from(address.0)?; + storage_diffs.insert(address, IndexMap::new()); + + for (key, value) in storage_mapping { + let storage_key = StorageKey::try_from(key.0)?; + let storage_value = value.0; + storage_diffs.entry(address).and_modify(|changes| { + changes.insert(storage_key, storage_value); + }); + } + } + + let mut nonces = IndexMap::new(); + for (address, nonce) in state_diff.address_to_nonce { + let address = ContractAddress::try_from(address.0)?; + let nonce = Nonce(nonce.0); + nonces.insert(address, nonce); + } + + Ok(Self { + deployed_contracts, + storage_diffs, + declared_classes: IndexMap::new(), + deprecated_declared_classes: IndexMap::new(), + nonces, + replaced_classes: IndexMap::new(), + }) + } +} + +impl From for PyStateDiff { + fn from(state_diff: CommitmentStateDiff) -> Self { + // State commitment. + let address_to_class_hash = state_diff + .address_to_class_hash + .iter() + .map(|(address, class_hash)| (PyFelt::from(*address), PyFelt::from(*class_hash))) + .collect(); + + let address_to_nonce = state_diff + .address_to_nonce + .iter() + .map(|(address, nonce)| (PyFelt::from(*address), PyFelt(nonce.0))) + .collect(); + + let storage_updates = state_diff + .storage_updates + .iter() + .map(|(address, storage_diff)| { + ( + PyFelt::from(*address), + storage_diff + .iter() + .map(|(key, value)| (PyFelt(*key.0.key()), PyFelt(*value))) + .collect(), + ) + }) + .collect(); + + // Declared classes commitment + let class_hash_to_compiled_class_hash = state_diff + .class_hash_to_compiled_class_hash + .iter() + .map(|(class_hash, compiled_class_hash)| { + (PyFelt::from(*class_hash), PyFelt::from(*compiled_class_hash)) + }) + .collect(); + + Self { + address_to_class_hash, + address_to_nonce, + storage_updates, + class_hash_to_compiled_class_hash, + } + } +} + +#[derive(Default, FromPyObject)] +pub struct PyResourcePrice { + pub price_in_wei: u128, + pub price_in_fri: u128, +} + +#[derive(FromPyObject)] +pub struct PyBlockInfo { + pub block_number: u64, + pub block_timestamp: u64, + pub l1_gas_price: PyResourcePrice, + pub l1_data_gas_price: PyResourcePrice, + pub sequencer_address: PyFelt, + pub use_kzg_da: bool, +} + +/// Block info cannot have gas prices set to zero; implement `Default` explicitly. +impl Default for PyBlockInfo { + fn default() -> Self { + Self { + block_number: u64::default(), + block_timestamp: u64::default(), + l1_gas_price: PyResourcePrice { + price_in_wei: DEFAULT_ETH_L1_GAS_PRICE, + price_in_fri: DEFAULT_STRK_L1_GAS_PRICE, + }, + l1_data_gas_price: PyResourcePrice { + price_in_wei: DEFAULT_ETH_L1_DATA_GAS_PRICE, + price_in_fri: DEFAULT_STRK_L1_DATA_GAS_PRICE, + }, + sequencer_address: PyFelt::default(), + use_kzg_da: bool::default(), + } + } +} diff --git a/crates/native_blockifier/src/py_test_utils.rs b/crates/native_blockifier/src/py_test_utils.rs new file mode 100644 index 00000000000..7ec663512fd --- /dev/null +++ b/crates/native_blockifier/src/py_test_utils.rs @@ -0,0 +1,23 @@ +use std::collections::HashMap; +use std::convert::TryFrom; + +use blockifier::execution::contract_class::ContractClassV0; +use blockifier::state::cached_state::CachedState; +use blockifier::test_utils::dict_state_reader::DictStateReader; +use starknet_api::class_hash; +use starknet_api::core::ClassHash; +use starknet_api::hash::StarkHash; + +pub const TOKEN_FOR_TESTING_CLASS_HASH: &str = "0x30"; +// This package is run within the StarkWare repository build directory. +pub const TOKEN_FOR_TESTING_CONTRACT_PATH: &str = + "./src/starkware/starknet/core/test_contract/starknet_compiled_contracts_lib/starkware/\ + starknet/core/test_contract/token_for_testing.json"; + +pub fn create_py_test_state() -> CachedState { + let class_hash_to_class = HashMap::from([( + class_hash!(TOKEN_FOR_TESTING_CLASS_HASH), + ContractClassV0::from_file(TOKEN_FOR_TESTING_CONTRACT_PATH).into(), + )]); + CachedState::from(DictStateReader { class_hash_to_class, ..Default::default() }) +} diff --git a/crates/native_blockifier/src/py_testing_wrappers.rs b/crates/native_blockifier/src/py_testing_wrappers.rs new file mode 100644 index 00000000000..f0d9f607e0d --- /dev/null +++ b/crates/native_blockifier/src/py_testing_wrappers.rs @@ -0,0 +1,37 @@ +use blockifier::execution::contract_class::estimate_casm_hash_computation_resources; +use blockifier::transaction::errors::{TransactionExecutionError, TransactionFeeError}; +use cairo_lang_starknet_classes::NestedIntList; +use pyo3::{pyfunction, PyResult}; + +use crate::errors::NativeBlockifierResult; +use crate::py_objects::PyExecutionResources; + +#[pyfunction] +pub fn raise_error_for_testing() -> NativeBlockifierResult<()> { + Err(TransactionExecutionError::TransactionFeeError( + TransactionFeeError::CairoResourcesNotContainedInFeeCosts, + ) + .into()) +} + +/// Wrapper for [estimate_casm_hash_computation_resources] that can be used for testing. +/// Takes a leaf. +#[pyfunction] +pub fn estimate_casm_hash_computation_resources_for_testing_single( + bytecode_segment_lengths: usize, +) -> PyResult { + let node = NestedIntList::Leaf(bytecode_segment_lengths); + Ok(estimate_casm_hash_computation_resources(&node).into()) +} + +/// Wrapper for [estimate_casm_hash_computation_resources] that can be used for testing. +/// Takes a node of leaves. +#[pyfunction] +pub fn estimate_casm_hash_computation_resources_for_testing_list( + bytecode_segment_lengths: Vec, +) -> PyResult { + let node = NestedIntList::Node( + bytecode_segment_lengths.into_iter().map(NestedIntList::Leaf).collect(), + ); + Ok(estimate_casm_hash_computation_resources(&node).into()) +} diff --git a/crates/native_blockifier/src/py_transaction.rs b/crates/native_blockifier/src/py_transaction.rs new file mode 100644 index 00000000000..7b84d89d71f --- /dev/null +++ b/crates/native_blockifier/src/py_transaction.rs @@ -0,0 +1,183 @@ +use std::collections::BTreeMap; + +use blockifier::execution::contract_class::{ + ClassInfo, + ContractClass, + ContractClassV0, + ContractClassV1, +}; +use blockifier::transaction::account_transaction::AccountTransaction; +use blockifier::transaction::transaction_execution::Transaction; +use blockifier::transaction::transaction_types::TransactionType; +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; +use starknet_api::transaction::{Resource, ResourceBounds}; +use starknet_api::StarknetApiError; + +use crate::errors::{NativeBlockifierInputError, NativeBlockifierResult}; +use crate::py_declare::py_declare; +use crate::py_deploy_account::py_deploy_account; +use crate::py_invoke_function::py_invoke_function; +use crate::py_l1_handler::py_l1_handler; + +pub(crate) const PY_TX_PARSING_ERR: &str = "Failed parsing Py transaction."; + +// Structs. + +#[derive(Clone, Eq, Ord, PartialEq, PartialOrd)] +pub enum PyResource { + L1Gas, + L2Gas, +} + +impl From for starknet_api::transaction::Resource { + fn from(py_resource: PyResource) -> Self { + match py_resource { + PyResource::L1Gas => starknet_api::transaction::Resource::L1Gas, + PyResource::L2Gas => starknet_api::transaction::Resource::L2Gas, + } + } +} + +impl FromPyObject<'_> for PyResource { + fn extract(resource: &PyAny) -> PyResult { + let resource_name: &str = resource.getattr("name")?.extract()?; + match resource_name { + "L1_GAS" => Ok(PyResource::L1Gas), + "L2_GAS" => Ok(PyResource::L2Gas), + _ => Err(PyValueError::new_err(format!("Invalid resource: {resource_name}"))), + } + } +} + +#[derive(Clone, Copy, Default, FromPyObject)] +pub struct PyResourceBounds { + pub max_amount: u64, + pub max_price_per_unit: u128, +} + +impl From for starknet_api::transaction::ResourceBounds { + fn from(py_resource_bounds: PyResourceBounds) -> Self { + Self { + max_amount: py_resource_bounds.max_amount, + max_price_per_unit: py_resource_bounds.max_price_per_unit, + } + } +} + +#[derive(Clone, FromPyObject)] +pub struct PyResourceBoundsMapping(pub BTreeMap); + +impl TryFrom for starknet_api::transaction::ResourceBoundsMapping { + type Error = StarknetApiError; + fn try_from(py_resource_bounds_mapping: PyResourceBoundsMapping) -> Result { + let resource_bounds_vec: Vec<(Resource, ResourceBounds)> = py_resource_bounds_mapping + .0 + .into_iter() + .map(|(py_resource_type, py_resource_bounds)| { + (Resource::from(py_resource_type), ResourceBounds::from(py_resource_bounds)) + }) + .collect(); + Self::try_from(resource_bounds_vec) + } +} + +#[derive(Clone)] +pub enum PyDataAvailabilityMode { + L1 = 0, + L2 = 1, +} + +impl FromPyObject<'_> for PyDataAvailabilityMode { + fn extract(data_availability_mode: &PyAny) -> PyResult { + let data_availability_mode: u8 = data_availability_mode.extract()?; + match data_availability_mode { + 0 => Ok(PyDataAvailabilityMode::L1), + 1 => Ok(PyDataAvailabilityMode::L2), + _ => Err(PyValueError::new_err(format!( + "Invalid data availability mode: {data_availability_mode}" + ))), + } + } +} + +impl From for starknet_api::data_availability::DataAvailabilityMode { + fn from(py_data_availability_mode: PyDataAvailabilityMode) -> Self { + match py_data_availability_mode { + PyDataAvailabilityMode::L1 => starknet_api::data_availability::DataAvailabilityMode::L1, + PyDataAvailabilityMode::L2 => starknet_api::data_availability::DataAvailabilityMode::L2, + } + } +} + +// Transaction creation. + +pub fn py_account_tx( + tx: &PyAny, + optional_py_class_info: Option, +) -> NativeBlockifierResult { + let Transaction::AccountTransaction(account_tx) = py_tx(tx, optional_py_class_info)? else { + panic!("Not an account transaction."); + }; + + Ok(account_tx) +} + +pub fn py_tx( + tx: &PyAny, + optional_py_class_info: Option, +) -> NativeBlockifierResult { + let tx_type = get_py_tx_type(tx)?; + let tx_type: TransactionType = + tx_type.parse().map_err(NativeBlockifierInputError::ParseError)?; + + Ok(match tx_type { + TransactionType::Declare => { + let non_optional_py_class_info: PyClassInfo = optional_py_class_info + .expect("A class info must be passed in a Declare transaction."); + AccountTransaction::Declare(py_declare(tx, non_optional_py_class_info)?).into() + } + TransactionType::DeployAccount => { + AccountTransaction::DeployAccount(py_deploy_account(tx)?).into() + } + TransactionType::InvokeFunction => { + AccountTransaction::Invoke(py_invoke_function(tx)?).into() + } + TransactionType::L1Handler => py_l1_handler(tx)?.into(), + }) +} + +pub fn get_py_tx_type(tx: &PyAny) -> NativeBlockifierResult<&str> { + Ok(tx.getattr("tx_type")?.getattr("name")?.extract()?) +} + +#[derive(FromPyObject)] +pub struct PyClassInfo { + raw_contract_class: String, + sierra_program_length: usize, + abi_length: usize, +} + +impl PyClassInfo { + pub fn try_from( + py_class_info: PyClassInfo, + tx: &starknet_api::transaction::DeclareTransaction, + ) -> NativeBlockifierResult { + let contract_class: ContractClass = match tx { + starknet_api::transaction::DeclareTransaction::V0(_) + | starknet_api::transaction::DeclareTransaction::V1(_) => { + ContractClassV0::try_from_json_string(&py_class_info.raw_contract_class)?.into() + } + starknet_api::transaction::DeclareTransaction::V2(_) + | starknet_api::transaction::DeclareTransaction::V3(_) => { + ContractClassV1::try_from_json_string(&py_class_info.raw_contract_class)?.into() + } + }; + let class_info = ClassInfo::new( + &contract_class, + py_class_info.sierra_program_length, + py_class_info.abi_length, + )?; + Ok(class_info) + } +} diff --git a/crates/native_blockifier/src/py_utils.rs b/crates/native_blockifier/src/py_utils.rs new file mode 100644 index 00000000000..e9ef4facaed --- /dev/null +++ b/crates/native_blockifier/src/py_utils.rs @@ -0,0 +1,100 @@ +use std::convert::TryFrom; + +use num_bigint::BigUint; +use pyo3::exceptions::PyValueError; +use pyo3::prelude::*; +use starknet_api::core::{ChainId, ClassHash, CompiledClassHash, ContractAddress, EthAddress}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +use crate::errors::NativeBlockifierResult; + +#[derive(Clone, Copy, Debug, Default, Eq, FromPyObject, Hash, PartialEq)] +pub struct PyFelt(#[pyo3(from_py_with = "int_to_stark_felt")] pub StarkFelt); + +impl IntoPy for PyFelt { + fn into_py(self, py: Python<'_>) -> PyObject { + BigUint::from_bytes_be(self.0.bytes()).into_py(py) + } +} + +impl From for PyFelt { + fn from(value: u64) -> Self { + Self(StarkFelt::from(value)) + } +} + +impl From for PyFelt { + fn from(value: u8) -> Self { + Self(StarkFelt::from(value)) + } +} + +impl From for PyFelt { + fn from(address: ContractAddress) -> Self { + Self(*address.0.key()) + } +} + +impl From for PyFelt { + fn from(address: EthAddress) -> Self { + let address_as_bytes: [u8; 20] = address.0.to_fixed_bytes(); + // Pad with 12 zeros. + let mut bytes = [0; 32]; + bytes[12..32].copy_from_slice(&address_as_bytes); + PyFelt(StarkFelt::new(bytes).expect("Convert Ethereum address to StarkFelt")) + } +} + +impl From for PyFelt { + fn from(class_hash: ClassHash) -> Self { + Self(class_hash.0) + } +} + +impl From for PyFelt { + fn from(compiled_class_hash: CompiledClassHash) -> Self { + Self(compiled_class_hash.0) + } +} + +impl From for PyFelt { + fn from(value: StorageKey) -> Self { + Self(*value.0.key()) + } +} + +fn int_to_stark_felt(int: &PyAny) -> PyResult { + let biguint: BigUint = int.extract()?; + biguint_to_felt(biguint).map_err(|e| PyValueError::new_err(e.to_string())) +} + +// TODO: Convert to a `TryFrom` cast and put in starknet-api (In StarkFelt). +pub fn biguint_to_felt(biguint: BigUint) -> NativeBlockifierResult { + let biguint_hex = format!("{biguint:#x}"); + Ok(StarkFelt::try_from(&*biguint_hex)?) +} + +pub fn to_py_vec(values: Vec, converter: F) -> Vec +where + F: FnMut(T) -> PyT, +{ + values.into_iter().map(converter).collect() +} + +pub fn from_py_felts(py_felts: Vec) -> Vec { + py_felts.into_iter().map(|felt| felt.0).collect() +} + +pub fn int_to_chain_id(int: &PyAny) -> PyResult { + let biguint: BigUint = int.extract()?; + Ok(ChainId(String::from_utf8_lossy(&biguint.to_bytes_be()).into())) +} + +pub fn py_attr(obj: &PyAny, attr: &str) -> NativeBlockifierResult +where + T: for<'a> FromPyObject<'a>, + T: Clone, +{ + Ok(obj.getattr(attr)?.extract()?) +} diff --git a/crates/native_blockifier/src/py_validator.rs b/crates/native_blockifier/src/py_validator.rs new file mode 100644 index 00000000000..635b0bd52e2 --- /dev/null +++ b/crates/native_blockifier/src/py_validator.rs @@ -0,0 +1,74 @@ +use blockifier::blockifier::stateful_validator::StatefulValidator; +use blockifier::bouncer::BouncerConfig; +use blockifier::context::BlockContext; +use blockifier::state::cached_state::CachedState; +use blockifier::versioned_constants::VersionedConstants; +use pyo3::{pyclass, pymethods, PyAny}; +use starknet_api::core::Nonce; +use starknet_api::transaction::TransactionHash; + +use crate::errors::NativeBlockifierResult; +use crate::py_block_executor::{into_block_context_args, PyGeneralConfig}; +use crate::py_state_diff::PyBlockInfo; +use crate::py_transaction::{py_account_tx, PyClassInfo, PY_TX_PARSING_ERR}; +use crate::py_utils::PyFelt; +use crate::state_readers::py_state_reader::PyStateReader; + +#[pyclass] +pub struct PyValidator { + pub stateful_validator: StatefulValidator, +} + +#[pymethods] +impl PyValidator { + #[new] + #[pyo3(signature = (general_config, state_reader_proxy, next_block_info, validate_max_n_steps, max_recursion_depth, max_nonce_for_validation_skip))] + pub fn create( + general_config: PyGeneralConfig, + state_reader_proxy: &PyAny, + next_block_info: PyBlockInfo, + validate_max_n_steps: u32, + max_recursion_depth: usize, + max_nonce_for_validation_skip: PyFelt, + ) -> NativeBlockifierResult { + // Create the state. + let state_reader = PyStateReader::new(state_reader_proxy); + let state = CachedState::new(state_reader); + + // Create the block context. + let (block_info, chain_info) = into_block_context_args(&general_config, &next_block_info)?; + let versioned_constants = VersionedConstants::latest_constants_with_overrides( + validate_max_n_steps, + max_recursion_depth, + ); + let block_context = + BlockContext::new_unchecked(&block_info, &chain_info, &versioned_constants); + + // Create the stateful validator. + let max_nonce_for_validation_skip = Nonce(max_nonce_for_validation_skip.0); + let stateful_validator = StatefulValidator::create( + state, + block_context, + max_nonce_for_validation_skip, + BouncerConfig::max(), + ); + + Ok(Self { stateful_validator }) + } + + // Transaction Execution API. + + #[pyo3(signature = (tx, optional_py_class_info, deploy_account_tx_hash))] + pub fn perform_validations( + &mut self, + tx: &PyAny, + optional_py_class_info: Option, + deploy_account_tx_hash: Option, + ) -> NativeBlockifierResult<()> { + let account_tx = py_account_tx(tx, optional_py_class_info).expect(PY_TX_PARSING_ERR); + let deploy_account_tx_hash = deploy_account_tx_hash.map(|hash| TransactionHash(hash.0)); + self.stateful_validator.perform_validations(account_tx, deploy_account_tx_hash)?; + + Ok(()) + } +} diff --git a/crates/native_blockifier/src/state_readers.rs b/crates/native_blockifier/src/state_readers.rs new file mode 100644 index 00000000000..f6ca71ad121 --- /dev/null +++ b/crates/native_blockifier/src/state_readers.rs @@ -0,0 +1,2 @@ +pub mod papyrus_state; +pub mod py_state_reader; diff --git a/crates/native_blockifier/src/state_readers/papyrus_state.rs b/crates/native_blockifier/src/state_readers/papyrus_state.rs new file mode 100644 index 00000000000..33d700c2788 --- /dev/null +++ b/crates/native_blockifier/src/state_readers/papyrus_state.rs @@ -0,0 +1,139 @@ +use blockifier::execution::contract_class::{ContractClass, ContractClassV0, ContractClassV1}; +use blockifier::state::errors::StateError; +use blockifier::state::global_cache::GlobalContractCache; +use blockifier::state::state_api::{StateReader, StateResult}; +use papyrus_storage::compiled_class::CasmStorageReader; +use papyrus_storage::db::RO; +use papyrus_storage::state::StateStorageReader; +use papyrus_storage::StorageReader; +use starknet_api::block::BlockNumber; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::{StateNumber, StorageKey}; + +#[cfg(test)] +#[path = "papyrus_state_test.rs"] +mod test; + +type RawPapyrusReader<'env> = papyrus_storage::StorageTxn<'env, RO>; + +pub struct PapyrusReader { + storage_reader: StorageReader, + latest_block: BlockNumber, + global_class_hash_to_class: GlobalContractCache, +} + +impl PapyrusReader { + pub fn new( + storage_reader: StorageReader, + latest_block: BlockNumber, + global_class_hash_to_class: GlobalContractCache, + ) -> Self { + Self { storage_reader, latest_block, global_class_hash_to_class } + } + + fn reader(&self) -> StateResult> { + self.storage_reader + .begin_ro_txn() + .map_err(|error| StateError::StateReadError(error.to_string())) + } + + /// Returns a V1 contract if found, or a V0 contract if a V1 contract is not + /// found, or an `Error` otherwise. + fn _get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { + let state_number = StateNumber(self.latest_block); + let class_declaration_block_number = self + .reader()? + .get_state_reader() + .and_then(|sr| sr.get_class_definition_block_number(&class_hash)) + .map_err(|err| StateError::StateReadError(err.to_string()))?; + let class_is_declared: bool = matches!(class_declaration_block_number, + Some(block_number) if block_number <= state_number.0); + + if class_is_declared { + let casm_contract_class = self + .reader()? + .get_casm(&class_hash) + .map_err(|err| StateError::StateReadError(err.to_string()))? + .expect( + "Should be able to fetch a Casm class if its definition exists, database is \ + inconsistent.", + ); + + return Ok(ContractClass::V1(ContractClassV1::try_from(casm_contract_class)?)); + } + + let v0_contract_class = self + .reader()? + .get_state_reader() + .and_then(|sr| sr.get_deprecated_class_definition_at(state_number, &class_hash)) + .map_err(|err| StateError::StateReadError(err.to_string()))?; + + match v0_contract_class { + Some(starknet_api_contract_class) => { + Ok(ContractClassV0::try_from(starknet_api_contract_class)?.into()) + } + None => Err(StateError::UndeclaredClassHash(class_hash)), + } + } +} + +// Currently unused - will soon replace the same `impl` for `PapyrusStateReader`. +impl StateReader for PapyrusReader { + fn get_storage_at( + &self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + let state_number = StateNumber(self.latest_block); + self.reader()? + .get_state_reader() + .and_then(|sr| sr.get_storage_at(state_number, &contract_address, &key)) + .map_err(|error| StateError::StateReadError(error.to_string())) + } + + fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult { + let state_number = StateNumber(self.latest_block); + match self + .reader()? + .get_state_reader() + .and_then(|sr| sr.get_nonce_at(state_number, &contract_address)) + { + Ok(Some(nonce)) => Ok(nonce), + Ok(None) => Ok(Nonce::default()), + Err(err) => Err(StateError::StateReadError(err.to_string())), + } + } + + fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { + let state_number = StateNumber(self.latest_block); + match self + .reader()? + .get_state_reader() + .and_then(|sr| sr.get_class_hash_at(state_number, &contract_address)) + { + Ok(Some(class_hash)) => Ok(class_hash), + Ok(None) => Ok(ClassHash::default()), + Err(err) => Err(StateError::StateReadError(err.to_string())), + } + } + + fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { + // Assumption: the global cache is cleared upon reverted blocks. + let contract_class = self.global_class_hash_to_class.get(&class_hash); + + match contract_class { + Some(contract_class) => Ok(contract_class), + None => { + let contract_class_from_db = self._get_compiled_contract_class(class_hash)?; + // The class was declared in a previous (finalized) state; update the global cache. + self.global_class_hash_to_class.set(class_hash, contract_class_from_db.clone()); + Ok(contract_class_from_db) + } + } + } + + fn get_compiled_class_hash(&self, _class_hash: ClassHash) -> StateResult { + todo!() + } +} diff --git a/crates/native_blockifier/src/state_readers/papyrus_state_test.rs b/crates/native_blockifier/src/state_readers/papyrus_state_test.rs new file mode 100644 index 00000000000..81e95be7f9b --- /dev/null +++ b/crates/native_blockifier/src/state_readers/papyrus_state_test.rs @@ -0,0 +1,71 @@ +use blockifier::abi::abi_utils::selector_from_name; +use blockifier::execution::call_info::{CallExecution, Retdata}; +use blockifier::execution::entry_point::CallEntryPoint; +use blockifier::retdata; +use blockifier::state::cached_state::CachedState; +use blockifier::state::global_cache::{GlobalContractCache, GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST}; +use blockifier::state::state_api::StateReader; +use blockifier::test_utils::contracts::FeatureContract; +use blockifier::test_utils::{trivial_external_entry_point_new, CairoVersion}; +use indexmap::IndexMap; +use papyrus_storage::class::ClassStorageWriter; +use papyrus_storage::state::StateStorageWriter; +use starknet_api::block::BlockNumber; +use starknet_api::hash::StarkFelt; +use starknet_api::state::{StorageKey, ThinStateDiff}; +use starknet_api::transaction::Calldata; +use starknet_api::{calldata, stark_felt}; + +use crate::state_readers::papyrus_state::PapyrusReader; + +#[test] +fn test_entry_point_with_papyrus_state() -> papyrus_storage::StorageResult<()> { + let ((storage_reader, mut storage_writer), _) = papyrus_storage::test_utils::get_test_storage(); + + let test_contract = FeatureContract::TestContract(CairoVersion::Cairo0); + let test_class_hash = test_contract.get_class_hash(); + // Initialize Storage: add test contract and class. + let deployed_contracts = + IndexMap::from([(test_contract.get_instance_address(0), test_class_hash)]); + let state_diff = ThinStateDiff { deployed_contracts, ..Default::default() }; + + let declared_classes = Vec::new(); + let deprecated_contract_class = test_contract.get_deprecated_contract_class(); + let deprecated_declared_classes = Vec::from([(test_class_hash, &deprecated_contract_class)]); + storage_writer + .begin_rw_txn()? + .append_classes(BlockNumber::default(), &declared_classes, &deprecated_declared_classes)? + .append_state_diff(BlockNumber::default(), state_diff)? + .commit()?; + + // BlockNumber is 1 due to the initialization step above. + let block_number = BlockNumber(1); + let papyrus_reader = PapyrusReader::new( + storage_reader, + block_number, + GlobalContractCache::new(GLOBAL_CONTRACT_CACHE_SIZE_FOR_TEST), + ); + let mut state = CachedState::from(papyrus_reader); + + // Call entrypoint that want to write to storage, which updates the cached state's write cache. + let key = stark_felt!(1234_u16); + let value = stark_felt!(18_u8); + let calldata = calldata![key, value]; + let entry_point_call = CallEntryPoint { + calldata, + entry_point_selector: selector_from_name("test_storage_read_write"), + ..trivial_external_entry_point_new(test_contract) + }; + let storage_address = entry_point_call.storage_address; + assert_eq!( + entry_point_call.execute_directly(&mut state).unwrap().execution, + CallExecution::from_retdata(retdata![value]) + ); + + // Verify that the state has changed. + let storage_key = StorageKey::try_from(key).unwrap(); + let value_from_state = state.get_storage_at(storage_address, storage_key).unwrap(); + assert_eq!(value_from_state, value); + + Ok(()) +} diff --git a/crates/native_blockifier/src/state_readers/py_state_reader.rs b/crates/native_blockifier/src/state_readers/py_state_reader.rs new file mode 100644 index 00000000000..15158b115fb --- /dev/null +++ b/crates/native_blockifier/src/state_readers/py_state_reader.rs @@ -0,0 +1,120 @@ +use blockifier::execution::contract_class::{ContractClass, ContractClassV0, ContractClassV1}; +use blockifier::state::errors::StateError; +use blockifier::state::state_api::{StateReader, StateResult}; +use pyo3::{FromPyObject, PyAny, PyErr, PyObject, PyResult, Python}; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +use crate::errors::{ + NativeBlockifierError, + NativeBlockifierInputError, + NativeBlockifierResult, + UndeclaredClassHashError, +}; +use crate::py_utils::PyFelt; + +// The value of Python StorageDomain.ON_CHAIN enum. +const ON_CHAIN_STORAGE_DOMAIN: u8 = 0; + +pub struct PyStateReader { + // A reference to an RsStateReaderProxy Python object. + // + // This is a reference to memory allocated on Python's heap and can outlive the GIL. + // Once PyObject is instantiated, the underlying Python object ref count is increased. + // Once it is dropped, the ref count is decreased the next time the GIL is acquired in pyo3. + state_reader_proxy: PyObject, +} + +impl PyStateReader { + pub fn new(state_reader_proxy: &PyAny) -> Self { + Self { state_reader_proxy: PyObject::from(state_reader_proxy) } + } +} + +impl StateReader for PyStateReader { + fn get_storage_at( + &self, + contract_address: ContractAddress, + key: StorageKey, + ) -> StateResult { + Python::with_gil(|py| -> PyResult { + let args = (ON_CHAIN_STORAGE_DOMAIN, PyFelt::from(contract_address), PyFelt::from(key)); + self.state_reader_proxy.as_ref(py).call_method1("get_storage_at", args)?.extract() + }) + .map(|felt| felt.0) + .map_err(|err| StateError::StateReadError(err.to_string())) + } + + fn get_nonce_at(&self, contract_address: ContractAddress) -> StateResult { + Python::with_gil(|py| -> PyResult { + let args = (ON_CHAIN_STORAGE_DOMAIN, PyFelt::from(contract_address)); + self.state_reader_proxy.as_ref(py).call_method1("get_nonce_at", args)?.extract() + }) + .map(|nonce| Nonce(nonce.0)) + .map_err(|err| StateError::StateReadError(err.to_string())) + } + + fn get_class_hash_at(&self, contract_address: ContractAddress) -> StateResult { + Python::with_gil(|py| -> PyResult { + let args = (PyFelt::from(contract_address),); + self.state_reader_proxy.as_ref(py).call_method1("get_class_hash_at", args)?.extract() + }) + .map(|felt| ClassHash(felt.0)) + .map_err(|err| StateError::StateReadError(err.to_string())) + } + + fn get_compiled_contract_class(&self, class_hash: ClassHash) -> StateResult { + Python::with_gil(|py| -> Result { + let args = (PyFelt::from(class_hash),); + let py_raw_compiled_class: PyRawCompiledClass = self + .state_reader_proxy + .as_ref(py) + .call_method1("get_raw_compiled_class", args)? + .extract()?; + + Ok(ContractClass::try_from(py_raw_compiled_class)?) + }) + .map_err(|err| { + if Python::with_gil(|py| err.is_instance_of::(py)) { + StateError::UndeclaredClassHash(class_hash) + } else { + StateError::StateReadError(err.to_string()) + } + }) + } + + fn get_compiled_class_hash(&self, class_hash: ClassHash) -> StateResult { + Python::with_gil(|py| -> PyResult { + let args = (PyFelt::from(class_hash),); + self.state_reader_proxy + .as_ref(py) + .call_method1("get_compiled_class_hash", args)? + .extract() + }) + .map(|felt| CompiledClassHash(felt.0)) + .map_err(|err| StateError::StateReadError(err.to_string())) + } +} + +#[derive(FromPyObject)] +pub struct PyRawCompiledClass { + pub raw_compiled_class: String, + pub version: usize, +} + +impl TryFrom for ContractClass { + type Error = NativeBlockifierError; + + fn try_from(raw_compiled_class: PyRawCompiledClass) -> NativeBlockifierResult { + match raw_compiled_class.version { + 0 => Ok(ContractClassV0::try_from_json_string(&raw_compiled_class.raw_compiled_class)? + .into()), + 1 => Ok(ContractClassV1::try_from_json_string(&raw_compiled_class.raw_compiled_class)? + .into()), + _ => Err(NativeBlockifierInputError::UnsupportedContractClassVersion { + version: raw_compiled_class.version, + })?, + } + } +} diff --git a/crates/native_blockifier/src/storage.rs b/crates/native_blockifier/src/storage.rs new file mode 100644 index 00000000000..39feeda948b --- /dev/null +++ b/crates/native_blockifier/src/storage.rs @@ -0,0 +1,304 @@ +use std::collections::HashMap; +use std::convert::TryFrom; +use std::path::PathBuf; + +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use indexmap::IndexMap; +use papyrus_storage::class::ClassStorageWriter; +use papyrus_storage::compiled_class::CasmStorageWriter; +use papyrus_storage::header::{HeaderStorageReader, HeaderStorageWriter}; +use papyrus_storage::state::{StateStorageReader, StateStorageWriter}; +use pyo3::prelude::*; +use starknet_api::block::{BlockHash, BlockHeader, BlockNumber}; +use starknet_api::core::{ChainId, ClassHash, CompiledClassHash, ContractAddress}; +use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; +use starknet_api::hash::StarkHash; +use starknet_api::state::{ContractClass, StateDiff, StateNumber, ThinStateDiff}; + +use crate::errors::NativeBlockifierResult; +use crate::py_state_diff::PyBlockInfo; +use crate::py_utils::{int_to_chain_id, PyFelt}; +use crate::PyStateDiff; + +const GENESIS_BLOCK_ID: u64 = u64::MAX; + +// Invariant: Only one instance of this struct should exist. +// Reader and writer fields must be cleared before the struct goes out of scope in Python; +// to prevent possible memory leaks (TODO: see if this is indeed necessary). +pub struct PapyrusStorage { + reader: Option, + writer: Option, +} + +impl PapyrusStorage { + pub fn new(config: StorageConfig) -> NativeBlockifierResult { + log::debug!("Initializing Blockifier storage..."); + let db_config = papyrus_storage::db::DbConfig { + path_prefix: config.path_prefix, + enforce_file_exists: config.enforce_file_exists, + chain_id: config.chain_id, + min_size: 1 << 20, // 1MB. + max_size: config.max_size, + growth_step: 1 << 26, // 64MB. + }; + let storage_config = papyrus_storage::StorageConfig { + db_config, + scope: papyrus_storage::StorageScope::StateOnly, // Only stores blockifier-related data. + // Storage for large objects (state-diffs, contracts). This sets total storage + // allocated, maximum space an object can take, and how fast the storage grows. + mmap_file_config: papyrus_storage::mmap_file::MmapFileConfig { + max_size: 1 << 40, // 1TB + growth_step: 2 << 30, // 2GB + max_object_size: 1 << 30, // 1GB + }, + }; + let (reader, writer) = papyrus_storage::open_storage(storage_config)?; + log::debug!("Initialized Blockifier storage."); + + Ok(PapyrusStorage { reader: Some(reader), writer: Some(writer) }) + } + + /// Manually drops the storage reader and writer. + /// Python does not necessarily drop them even if instance is no longer live. + pub fn new_for_testing(path_prefix: PathBuf, chain_id: &ChainId) -> PapyrusStorage { + let db_config = papyrus_storage::db::DbConfig { + path_prefix, + chain_id: chain_id.clone(), + enforce_file_exists: false, + min_size: 1 << 20, // 1MB + max_size: 1 << 35, // 32GB + growth_step: 1 << 26, // 64MB + }; + let storage_config = papyrus_storage::StorageConfig { db_config, ..Default::default() }; + let (reader, writer) = papyrus_storage::open_storage(storage_config).unwrap(); + + PapyrusStorage { reader: Some(reader), writer: Some(writer) } + } +} + +impl Storage for PapyrusStorage { + /// Returns the next block number, for which state diff was not yet appended. + fn get_state_marker(&self) -> NativeBlockifierResult { + let block_number = self.reader().begin_ro_txn()?.get_state_marker()?; + Ok(block_number.0) + } + + fn get_header_marker(&self) -> NativeBlockifierResult { + let block_number = self.reader().begin_ro_txn()?.get_header_marker()?; + Ok(block_number.0) + } + + fn get_block_id(&self, block_number: u64) -> NativeBlockifierResult>> { + let block_number = BlockNumber(block_number); + let block_hash = self + .reader() + .begin_ro_txn()? + .get_block_header(block_number)? + .map(|block_header| Vec::from(block_header.block_hash.0.bytes().as_slice())); + Ok(block_hash) + } + + fn revert_block(&mut self, block_number: u64) -> NativeBlockifierResult<()> { + log::debug!("Reverting state diff for {block_number:?}."); + let block_number = BlockNumber(block_number); + let revert_txn = self.writer().begin_rw_txn()?; + let (revert_txn, _) = revert_txn.revert_state_diff(block_number)?; + let (revert_txn, _, _) = revert_txn.revert_header(block_number)?; + + revert_txn.commit()?; + Ok(()) + } + + // TODO(Gilad): Refactor. + fn append_block( + &mut self, + block_id: u64, + previous_block_id: Option, + py_block_info: PyBlockInfo, + py_state_diff: PyStateDiff, + declared_class_hash_to_class: HashMap, + deprecated_declared_class_hash_to_class: HashMap, + ) -> NativeBlockifierResult<()> { + log::debug!( + "Appending state diff with {block_id:?} for block_number: {}.", + py_block_info.block_number + ); + let block_number = BlockNumber(py_block_info.block_number); + let state_number = StateNumber(block_number); + + // Deserialize contract classes. + let mut deprecated_declared_classes = IndexMap::::new(); + for (class_hash, raw_class) in deprecated_declared_class_hash_to_class { + let class_hash = ClassHash(class_hash.0); + let class_undeclared = self + .reader() + .begin_ro_txn()? + .get_state_reader()? + .get_deprecated_class_definition_at(state_number, &class_hash)? + .is_none(); + + if class_undeclared { + let deprecated_contract_class: DeprecatedContractClass = + serde_json::from_str(&raw_class)?; + deprecated_declared_classes.insert(class_hash, deprecated_contract_class); + } + } + + // Collect replaced classes (changed class hash of an already allocated address; + // i.e.: pointing to a non-zeroed class hash). Rest would be (newly) deployed classes. + let mut replaced_classes = IndexMap::::new(); + for (address, class_hash) in &py_state_diff.address_to_class_hash { + let address = ContractAddress::try_from(address.0)?; + let address_assigned: bool = self + .reader() + .begin_ro_txn()? + .get_state_reader()? + .get_class_hash_at(state_number, &address)? + .is_some(); + + if address_assigned { + replaced_classes.insert(address, ClassHash(class_hash.0)); + } + } + let mut py_state_diff = py_state_diff; + replaced_classes.keys().for_each(|&address| { + py_state_diff.address_to_class_hash.remove(&address.into()); + }); + + let mut declared_classes = IndexMap::::new(); + let mut undeclared_casm_contracts = Vec::<(ClassHash, CasmContractClass)>::new(); + for (class_hash, (compiled_class_hash, raw_class)) in declared_class_hash_to_class { + let class_hash = ClassHash(class_hash.0); + let class_undeclared = self + .reader() + .begin_ro_txn()? + .get_state_reader()? + .get_class_definition_at(state_number, &class_hash)? + .is_none(); + + if class_undeclared { + declared_classes.insert( + class_hash, + (CompiledClassHash(compiled_class_hash.0), ContractClass::default()), + ); + let contract_class: CasmContractClass = serde_json::from_str(&raw_class)?; + undeclared_casm_contracts.push((class_hash, contract_class)); + } + } + + let mut append_txn = self.writer().begin_rw_txn()?; + for (class_hash, contract_class) in undeclared_casm_contracts { + append_txn = append_txn.append_casm(&class_hash, &contract_class)?; + } + + // Construct state diff; manually add declared classes. + let mut state_diff = StateDiff::try_from(py_state_diff)?; + state_diff.deprecated_declared_classes = deprecated_declared_classes; + state_diff.declared_classes = declared_classes; + state_diff.replaced_classes = replaced_classes; + + let (thin_state_diff, declared_classes, deprecated_declared_classes) = + ThinStateDiff::from_state_diff(state_diff); + + let declared_classes_vec = + Vec::from_iter(declared_classes.iter().map(|(hash, class)| (*hash, class))); + let deprecated_declared_classes_vec = + Vec::from_iter(deprecated_declared_classes.iter().map(|(hash, class)| (*hash, class))); + append_txn = append_txn.append_classes( + block_number, + &declared_classes_vec, + &deprecated_declared_classes_vec, + )?; + append_txn = append_txn.append_state_diff(block_number, thin_state_diff)?; + + let previous_block_id = previous_block_id.unwrap_or_else(|| PyFelt::from(GENESIS_BLOCK_ID)); + let block_header = BlockHeader { + block_hash: BlockHash(StarkHash::from(block_id)), + parent_hash: BlockHash(previous_block_id.0), + block_number, + ..Default::default() + }; + append_txn = append_txn.append_header(block_number, &block_header)?; + + append_txn.commit()?; + Ok(()) + } + + fn validate_aligned(&self, source_block_number: u64) { + let header_marker = self.get_header_marker().expect("Should have a header marker"); + let state_marker = self.get_state_marker().expect("Should have a state marker"); + + assert_eq!( + header_marker, state_marker, + "Block header marker ({header_marker}) must be aligned to block state diff marker \ + ({state_marker}) before sequencing starts." + ); + + assert_eq!( + state_marker, source_block_number, + "Target storage (block number {state_marker}) should have been aligned to block \ + number {source_block_number}." + ); + } + + fn reader(&self) -> &papyrus_storage::StorageReader { + self.reader.as_ref().expect("Storage should be initialized.") + } + + fn writer(&mut self) -> &mut papyrus_storage::StorageWriter { + self.writer.as_mut().expect("Storage should be initialized.") + } + + fn close(&mut self) { + log::debug!("Closing Blockifier storage."); + self.reader = None; + self.writer = None; + } +} + +#[pyclass] +#[derive(Clone)] +pub struct StorageConfig { + path_prefix: PathBuf, + chain_id: ChainId, + enforce_file_exists: bool, + max_size: usize, +} + +#[pymethods] +impl StorageConfig { + #[new] + #[pyo3(signature = (path_prefix, chain_id, enforce_file_exists, max_size))] + pub fn new( + path_prefix: PathBuf, + #[pyo3(from_py_with = "int_to_chain_id")] chain_id: ChainId, + enforce_file_exists: bool, + max_size: usize, + ) -> Self { + Self { path_prefix, chain_id, enforce_file_exists, max_size } + } +} + +pub trait Storage { + fn get_state_marker(&self) -> NativeBlockifierResult; + fn get_header_marker(&self) -> NativeBlockifierResult; + fn get_block_id(&self, block_number: u64) -> NativeBlockifierResult>>; + + fn revert_block(&mut self, block_number: u64) -> NativeBlockifierResult<()>; + fn append_block( + &mut self, + block_id: u64, + previous_block_id: Option, + py_block_info: PyBlockInfo, + py_state_diff: PyStateDiff, + declared_class_hash_to_class: HashMap, + deprecated_declared_class_hash_to_class: HashMap, + ) -> NativeBlockifierResult<()>; + + fn validate_aligned(&self, source_block_number: u64); + + fn reader(&self) -> &papyrus_storage::StorageReader; + fn writer(&mut self) -> &mut papyrus_storage::StorageWriter; + + fn close(&mut self); +} diff --git a/crates/native_blockifier/src/test_utils.rs b/crates/native_blockifier/src/test_utils.rs new file mode 100644 index 00000000000..13a2626ecc4 --- /dev/null +++ b/crates/native_blockifier/src/test_utils.rs @@ -0,0 +1,58 @@ +use std::collections::HashMap; + +use crate::errors::NativeBlockifierResult; +use crate::storage::Storage; + +pub struct MockStorage { + pub block_number_to_class_hash: HashMap>, + // .. Add more as needed. +} + +impl Storage for MockStorage { + fn get_block_id(&self, block_number: u64) -> NativeBlockifierResult>> { + Ok(self.block_number_to_class_hash.get(&block_number).cloned()) + } + + fn get_state_marker(&self) -> NativeBlockifierResult { + todo!() + } + + fn get_header_marker(&self) -> NativeBlockifierResult { + todo!() + } + + fn revert_block(&mut self, _block_number: u64) -> NativeBlockifierResult<()> { + todo!() + } + + fn append_block( + &mut self, + _block_id: u64, + _previous_block_id: Option, + _py_block_info: crate::py_state_diff::PyBlockInfo, + _py_state_diff: crate::py_state_diff::PyStateDiff, + _declared_class_hash_to_class: HashMap< + crate::py_utils::PyFelt, + (crate::py_utils::PyFelt, String), + >, + _deprecated_declared_class_hash_to_class: HashMap, + ) -> NativeBlockifierResult<()> { + todo!() + } + + fn validate_aligned(&self, _source_block_number: u64) { + todo!() + } + + fn reader(&self) -> &papyrus_storage::StorageReader { + todo!() + } + + fn writer(&mut self) -> &mut papyrus_storage::StorageWriter { + todo!() + } + + fn close(&mut self) { + todo!() + } +} diff --git a/crates/papyrus_base_layer/Cargo.toml b/crates/papyrus_base_layer/Cargo.toml index bb6c25a5596..e69c94a1957 100644 --- a/crates/papyrus_base_layer/Cargo.toml +++ b/crates/papyrus_base_layer/Cargo.toml @@ -12,7 +12,7 @@ papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } rustc-hex.workspace = true serde.workspace = true serde_json.workspace = true -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } url.workspace = true @@ -20,7 +20,7 @@ url.workspace = true [dev-dependencies] ethers-core = { version = "2.0.3" } pretty_assertions.workspace = true -starknet_api = { workspace = true, features = ["testing"] } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } tar = { version = "0.4.38" } tempfile.workspace = true test-with = { version = "0.9.3", default-features = false, features = [ diff --git a/crates/papyrus_common/Cargo.toml b/crates/papyrus_common/Cargo.toml index 644b77bc0de..43e64b4dada 100644 --- a/crates/papyrus_common/Cargo.toml +++ b/crates/papyrus_common/Cargo.toml @@ -14,12 +14,14 @@ lazy_static.workspace = true serde.workspace = true serde_json.workspace = true sha3.workspace = true -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } starknet-crypto.workspace = true thiserror.workspace = true +rand.workspace = true +indexmap.workspace = true [dev-dependencies] assert_matches.workspace = true pretty_assertions.workspace = true serde_json = { workspace = true, features = ["arbitrary_precision"]} -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/papyrus_common/src/block_hash.rs b/crates/papyrus_common/src/block_hash.rs index 88941d5153f..f49719d9c22 100644 --- a/crates/papyrus_common/src/block_hash.rs +++ b/crates/papyrus_common/src/block_hash.rs @@ -5,7 +5,12 @@ mod block_hash_test; use std::iter::zip; use starknet_api::block::{BlockBody, BlockHash, BlockHeader}; -use starknet_api::core::{ChainId, EventCommitment, TransactionCommitment}; +use starknet_api::core::{ + ChainId, + EventCommitment, + SequencerContractAddress, + TransactionCommitment, +}; use starknet_api::hash::{pedersen_hash, StarkFelt, StarkHash}; use starknet_api::transaction::{ DeployAccountTransaction, @@ -93,11 +98,11 @@ fn calculate_block_hash_by_version( .chain(&header.state_root.0) .chain_if_fn( || { - if version == BlockHashVersion::V2 { - Some(get_chain_sequencer_address(chain_id)) - } else { - Some(*header.sequencer.0.key()) + if header.sequencer != SequencerContractAddress::default() || version != BlockHashVersion::V2 { + return Some(*header.sequencer.0.key()); } + // V2 block with no sequencer address. + Some(get_chain_sequencer_address(chain_id)) } ) .chain_if_fn(|| { diff --git a/crates/papyrus_common/src/block_hash_test.rs b/crates/papyrus_common/src/block_hash_test.rs index 015596f0b14..810d0a07fdc 100644 --- a/crates/papyrus_common/src/block_hash_test.rs +++ b/crates/papyrus_common/src/block_hash_test.rs @@ -1,7 +1,7 @@ use assert_matches::assert_matches; +use papyrus_test_utils::read_json_file; use starknet_api::block::Block; use starknet_api::core::ChainId; -use test_utils::read_json_file; use crate::block_hash::{ calculate_block_hash_by_version, diff --git a/crates/papyrus_common/src/class_hash_test.rs b/crates/papyrus_common/src/class_hash_test.rs index a99a43ac8ab..3a5a5581ff0 100644 --- a/crates/papyrus_common/src/class_hash_test.rs +++ b/crates/papyrus_common/src/class_hash_test.rs @@ -1,8 +1,8 @@ +use papyrus_test_utils::read_json_file; use starknet_api::class_hash; use starknet_api::core::ClassHash; use starknet_api::hash::StarkHash; use starknet_api::state::ContractClass; -use test_utils::read_json_file; use crate::class_hash::calculate_class_hash; diff --git a/crates/papyrus_common/src/state.rs b/crates/papyrus_common/src/state.rs index 528ced98e4e..119f7b86d11 100644 --- a/crates/papyrus_common/src/state.rs +++ b/crates/papyrus_common/src/state.rs @@ -1,7 +1,9 @@ +use indexmap::indexmap; +use rand::RngCore; use serde::{Deserialize, Serialize}; -use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress}; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::hash::StarkFelt; -use starknet_api::state::StorageKey; +use starknet_api::state::{StorageKey, ThinStateDiff}; /// A storage entry in a contract. #[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] @@ -29,3 +31,31 @@ pub struct ReplacedClass { pub address: ContractAddress, pub class_hash: ClassHash, } + +pub fn create_random_state_diff(rng: &mut impl RngCore) -> ThinStateDiff { + let contract0 = ContractAddress::from(rng.next_u64()); + let contract1 = ContractAddress::from(rng.next_u64()); + let contract2 = ContractAddress::from(rng.next_u64()); + let class_hash = ClassHash(rng.next_u64().into()); + let compiled_class_hash = CompiledClassHash(rng.next_u64().into()); + let deprecated_class_hash = ClassHash(rng.next_u64().into()); + ThinStateDiff { + deployed_contracts: indexmap! { + contract0 => class_hash, contract1 => class_hash, contract2 => deprecated_class_hash + }, + storage_diffs: indexmap! { + contract0 => indexmap! { + 1u64.into() => StarkFelt::ONE, 2u64.into() => StarkFelt::TWO + }, + contract1 => indexmap! { + 3u64.into() => StarkFelt::TWO, 4u64.into() => StarkFelt::ONE + }, + }, + declared_classes: indexmap! { class_hash => compiled_class_hash }, + deprecated_declared_classes: vec![deprecated_class_hash], + nonces: indexmap! { + contract0 => Nonce(StarkFelt::ONE), contract2 => Nonce(StarkFelt::TWO) + }, + replaced_classes: Default::default(), + } +} diff --git a/crates/papyrus_common/src/transaction_hash_test.rs b/crates/papyrus_common/src/transaction_hash_test.rs index f08561bf2b8..b40a32f54de 100644 --- a/crates/papyrus_common/src/transaction_hash_test.rs +++ b/crates/papyrus_common/src/transaction_hash_test.rs @@ -1,3 +1,4 @@ +use papyrus_test_utils::read_json_file; use pretty_assertions::assert_eq; use serde::{Deserialize, Serialize}; use sha3::{Digest, Keccak256}; @@ -5,7 +6,6 @@ use starknet_api::block::BlockNumber; use starknet_api::core::ChainId; use starknet_api::hash::StarkFelt; use starknet_api::transaction::{Transaction, TransactionHash}; -use test_utils::read_json_file; use super::{ ascii_as_felt, diff --git a/crates/papyrus_config/Cargo.toml b/crates/papyrus_config/Cargo.toml index 13054840b70..34f69bd71ba 100644 --- a/crates/papyrus_config/Cargo.toml +++ b/crates/papyrus_config/Cargo.toml @@ -23,4 +23,4 @@ assert_matches.workspace = true itertools.workspace = true lazy_static.workspace = true tempfile.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/papyrus_config/src/config_test.rs b/crates/papyrus_config/src/config_test.rs index 4fdff4a8886..505ac571b16 100644 --- a/crates/papyrus_config/src/config_test.rs +++ b/crates/papyrus_config/src/config_test.rs @@ -8,10 +8,10 @@ use assert_matches::assert_matches; use clap::Command; use itertools::chain; use lazy_static::lazy_static; +use papyrus_test_utils::get_absolute_path; use serde::{Deserialize, Serialize}; use serde_json::json; use tempfile::TempDir; -use test_utils::get_absolute_path; use validator::Validate; use crate::command::{get_command_matches, update_config_map_by_command_args}; diff --git a/crates/papyrus_execution/Cargo.toml b/crates/papyrus_execution/Cargo.toml index c09dfbeb2a5..edd61e3be78 100644 --- a/crates/papyrus_execution/Cargo.toml +++ b/crates/papyrus_execution/Cargo.toml @@ -7,11 +7,11 @@ license-file.workspace = true description = "Transaction and entry point execution functionality for a Papyrus node." [features] -testing = ["rand", "rand_chacha", "test_utils"] +testing = ["rand", "rand_chacha", "papyrus_test_utils"] [dependencies] anyhow.workspace = true -blockifier.workspace = true +blockifier = { version = "0.7.0-dev.1" } cairo-lang-starknet-classes.workspace = true cairo-vm.workspace = true indexmap.workspace = true @@ -25,8 +25,8 @@ rand = { workspace = true, optional = true } rand_chacha = { workspace = true, optional = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api.workspace = true -test_utils = { path = "../test_utils", optional = true } +starknet_api = { version = "0.12.0-dev.1" } +papyrus_test_utils = { path = "../papyrus_test_utils", optional = true } thiserror.workspace = true tracing.workspace = true @@ -39,4 +39,4 @@ papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } pretty_assertions.workspace = true rand.workspace = true rand_chacha.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/papyrus_execution/src/test_utils.rs b/crates/papyrus_execution/src/test_utils.rs index 904bda61da7..a1f6bcf6757 100644 --- a/crates/papyrus_execution/src/test_utils.rs +++ b/crates/papyrus_execution/src/test_utils.rs @@ -10,6 +10,7 @@ use papyrus_storage::compiled_class::CasmStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::{StorageReader, StorageWriter}; +use papyrus_test_utils::read_json_file; use serde::de::DeserializeOwned; use starknet_api::block::{ BlockBody, @@ -44,7 +45,6 @@ use starknet_api::transaction::{ TransactionHash, }; use starknet_api::{calldata, class_hash, contract_address, patricia_key, stark_felt}; -use test_utils::read_json_file; use crate::execution_utils::selector_from_name; use crate::objects::{PendingData, TransactionSimulationOutput}; diff --git a/crates/papyrus_execution/src/testing_instances.rs b/crates/papyrus_execution/src/testing_instances.rs index 4a362a6a04a..7b7eba47625 100644 --- a/crates/papyrus_execution/src/testing_instances.rs +++ b/crates/papyrus_execution/src/testing_instances.rs @@ -3,13 +3,13 @@ /// Returns the storage key of a storage variable. pub use blockifier::abi::abi_utils::get_storage_var_address; +use papyrus_test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use starknet_api::block::GasPrice; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, PatriciaKey}; use starknet_api::deprecated_contract_class::EntryPointType; use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::transaction::{Calldata, EventContent, ExecutionResources, Fee, MessageToL1}; use starknet_api::{contract_address, patricia_key}; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use crate::objects::{ CallType, diff --git a/crates/papyrus_network/Cargo.toml b/crates/papyrus_network/Cargo.toml index e4aaf07ad39..a426974afba 100644 --- a/crates/papyrus_network/Cargo.toml +++ b/crates/papyrus_network/Cargo.toml @@ -21,6 +21,7 @@ futures.workspace = true indexmap.workspace = true lazy_static.workspace = true libp2p = { workspace = true, features = [ + "gossipsub", "identify", "kad", "macros", @@ -35,11 +36,11 @@ metrics.workspace = true replace_with.workspace = true papyrus_common = { path = "../papyrus_common", version = "0.4.0-dev.2" } papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } +papyrus_protobuf = { path = "../papyrus_protobuf", version = "0.4.0-dev.2" } papyrus_storage = { path = "../papyrus_storage", version = "0.4.0-dev.2" } prost.workspace = true -prost-types.workspace = true serde = { workspace = true, features = ["derive"] } -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } tracing.workspace = true @@ -49,9 +50,6 @@ unsigned-varint = { workspace = true, features = ["std"] } clap = { workspace = true, optional = true, features = ["derive"] } -[build-dependencies] -prost-build.workspace = true - [dev-dependencies] assert_matches.workspace = true deadqueue = { workspace = true, features = ["unlimited"] } @@ -61,5 +59,6 @@ mockall.workspace = true papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } pretty_assertions.workspace = true rand.workspace = true +tokio = { workspace = true, features = ["full", "sync", "test-util"] } tokio-stream.workspace = true void.workspace = true diff --git a/crates/papyrus_network/build.rs b/crates/papyrus_network/build.rs deleted file mode 100644 index 8a7b721ea55..00000000000 --- a/crates/papyrus_network/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use std::io::Result; - -fn main() -> Result<()> { - println!("Building"); - prost_build::compile_protos( - &[ - "src/protobuf_messages/proto/p2p/proto/header.proto", - "src/protobuf_messages/proto/p2p/proto/state.proto", - ], - &["src/protobuf_messages/proto/"], - )?; - Ok(()) -} diff --git a/crates/papyrus_network/src/bin/streamed_bytes_benchmark.rs b/crates/papyrus_network/src/bin/streamed_bytes_benchmark.rs index 2148d001db9..0568c320409 100644 --- a/crates/papyrus_network/src/bin/streamed_bytes_benchmark.rs +++ b/crates/papyrus_network/src/bin/streamed_bytes_benchmark.rs @@ -6,15 +6,9 @@ use futures::StreamExt; use libp2p::swarm::SwarmEvent; use libp2p::{PeerId, StreamProtocol, Swarm}; use papyrus_network::bin_utils::{build_swarm, dial}; -use papyrus_network::streamed_bytes::behaviour::{Behaviour, Event, ExternalEvent, SessionError}; -use papyrus_network::streamed_bytes::messages::with_length_prefix; -use papyrus_network::streamed_bytes::{ - Bytes, - Config, - InboundSessionId, - OutboundSessionId, - SessionId, -}; +use papyrus_network::sqmr::behaviour::{Behaviour, Event, ExternalEvent, SessionError}; +use papyrus_network::sqmr::messages::with_length_prefix; +use papyrus_network::sqmr::{Bytes, Config, InboundSessionId, OutboundSessionId, SessionId}; const PROTOCOL_NAME: StreamProtocol = StreamProtocol::new("/papyrus/bench/1"); const CONST_BYTE: u8 = 1; diff --git a/crates/papyrus_network/src/bin_utils/mod.rs b/crates/papyrus_network/src/bin_utils/mod.rs index 46b7abb1d60..c0f8243e896 100644 --- a/crates/papyrus_network/src/bin_utils/mod.rs +++ b/crates/papyrus_network/src/bin_utils/mod.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::time::Duration; -use libp2p::identity::{Keypair, PublicKey}; +use libp2p::identity::Keypair; use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::NetworkBehaviour; use libp2p::{noise, yamux, Multiaddr, Swarm, SwarmBuilder}; @@ -10,7 +10,7 @@ use tracing::debug; pub fn build_swarm( listen_addresses: Vec, idle_connection_timeout: Duration, - behaviour: impl Fn(PublicKey) -> Behaviour, + behaviour: impl Fn(Keypair) -> Behaviour, ) -> Swarm where { @@ -27,7 +27,7 @@ where .expect("Error building TCP transport") // TODO: quic transpot does not work (failure appears in the command line when running in debug mode) // .with_quic() - .with_behaviour(|key| behaviour(key.public())) + .with_behaviour(|key| behaviour(key.clone())) .expect("Error while building the swarm") .with_swarm_config(|cfg| cfg.with_idle_connection_timeout(idle_connection_timeout)) .build(); diff --git a/crates/papyrus_network/src/converters/mod.rs b/crates/papyrus_network/src/converters/mod.rs index 10060f13501..888b7589c87 100644 --- a/crates/papyrus_network/src/converters/mod.rs +++ b/crates/papyrus_network/src/converters/mod.rs @@ -1,15 +1,12 @@ -pub mod protobuf_conversion; - -#[cfg(test)] -mod test; - use std::collections::HashMap; use futures::channel::mpsc::{Receiver, Sender}; use futures::StreamExt; +use papyrus_protobuf::protobuf; +use papyrus_protobuf::sync::DataOrFin; use prost::Message; +use starknet_api::state::ThinStateDiff; -use crate::protobuf_messages::protobuf::{self}; use crate::{Protocol, ResponseReceivers}; impl ResponseReceivers { @@ -31,10 +28,12 @@ impl ResponseReceivers { protocol_to_receiver_map.remove(&Protocol::StateDiff).map(|receiver| { receiver .map(|data_bytes| { - protobuf::StateDiffsResponse::decode(&data_bytes[..]) - .expect("failed to decode protobuf StateDiff") - .try_into() - .expect("failed to convert ThinStateDiff") + DataOrFin::::try_from( + protobuf::StateDiffsResponse::decode(&data_bytes[..]) + .expect("failed to decode protobuf StateDiff"), + ) + .expect("failed to convert ThinStateDiff") + .0 }) .boxed() }); diff --git a/crates/papyrus_network/src/converters/protobuf_conversion/mod.rs b/crates/papyrus_network/src/converters/protobuf_conversion/mod.rs deleted file mode 100644 index d583f25e418..00000000000 --- a/crates/papyrus_network/src/converters/protobuf_conversion/mod.rs +++ /dev/null @@ -1,19 +0,0 @@ -mod common; -mod header; -pub mod state_diff; - -#[derive(thiserror::Error, Debug)] -pub enum ProtobufConversionError { - #[error("Type `{type_description}` got out of range value {value_as_str}")] - OutOfRangeValue { type_description: &'static str, value_as_str: String }, - #[error("Missing field `{field_description}`")] - MissingField { field_description: &'static str }, - #[error("Type `{type_description}` should be {num_expected} bytes but it got {value:?}.")] - BytesDataLengthMismatch { type_description: &'static str, num_expected: usize, value: Vec }, -} - -#[derive(thiserror::Error, Debug)] -pub enum ProtobufResponseToDataError { - #[error("Type `{type_description}` got unsupported data type {data_type}")] - UnsupportedDataType { data_type: String, type_description: String }, -} diff --git a/crates/papyrus_network/src/converters/protobuf_conversion/state_diff.rs b/crates/papyrus_network/src/converters/protobuf_conversion/state_diff.rs deleted file mode 100644 index bef0a2012f5..00000000000 --- a/crates/papyrus_network/src/converters/protobuf_conversion/state_diff.rs +++ /dev/null @@ -1,238 +0,0 @@ -use indexmap::IndexMap; -use starknet_api::core::{ClassHash, CompiledClassHash, Nonce}; -use starknet_api::hash::StarkFelt; -use starknet_api::state::{StorageKey, ThinStateDiff}; - -use super::ProtobufConversionError; -use crate::protobuf_messages::protobuf; -use crate::{InternalQuery, Query}; - -impl TryFrom for Option { - type Error = ProtobufConversionError; - fn try_from(value: protobuf::StateDiffsResponse) -> Result { - match value.state_diff_message { - Some(protobuf::state_diffs_response::StateDiffMessage::ContractDiff(contract_diff)) => { - Ok(Some(contract_diff.try_into()?)) - } - Some(protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( - declared_class, - )) => Ok(Some(declared_class.try_into()?)), - Some(protobuf::state_diffs_response::StateDiffMessage::Fin(_)) => Ok(None), - None => Err(ProtobufConversionError::MissingField { - field_description: "StateDiffsResponse::state_diff_message", - }), - } - } -} - -impl TryFrom for ThinStateDiff { - type Error = ProtobufConversionError; - fn try_from(value: protobuf::ContractDiff) -> Result { - let contract_address = value - .address - .ok_or(ProtobufConversionError::MissingField { - field_description: "ContractDiff::address", - })? - .try_into()?; - - let deployed_contracts = value - .class_hash - .map(|hash| Ok(IndexMap::from_iter([(contract_address, ClassHash(hash.try_into()?))]))) - .transpose()? - .unwrap_or_default(); - - let storage_diffs = if value.values.is_empty() { - IndexMap::new() - } else { - let storage_values = value - .values - .into_iter() - .map(|stored_value| stored_value.try_into()) - .collect::, _>>()?; - IndexMap::from_iter([(contract_address, storage_values)]) - }; - - let nonces = value - .nonce - .map(|nonce| Ok(IndexMap::from_iter([(contract_address, Nonce(nonce.try_into()?))]))) - .transpose()? - .unwrap_or_default(); - - // TODO(shahak): Use the domain field once Starknet supports volition. - - Ok(ThinStateDiff { - deployed_contracts, - storage_diffs, - nonces, - // These two fields come from DeclaredClass messages. - declared_classes: Default::default(), - deprecated_declared_classes: Default::default(), - // The p2p specs doesn't separate replaced classes from deployed contracts. In RPC v0.8 - // the node will stop separating them as well. Until then nodes syncing from - // P2P won't be able to separate replaced classes from deployed contracts correctly - replaced_classes: Default::default(), - }) - } -} - -impl TryFrom for ThinStateDiff { - type Error = ProtobufConversionError; - fn try_from(value: protobuf::DeclaredClass) -> Result { - let class_hash = ClassHash( - value - .class_hash - .ok_or(ProtobufConversionError::MissingField { - field_description: "DeclaredClass::class_hash", - })? - .try_into()?, - ); - - // According to the P2P specs, if compiled_class_hash is missing, the declared class is a - // cairo-0 class. - match value.compiled_class_hash { - Some(compiled_class_hash) => Ok(ThinStateDiff { - declared_classes: IndexMap::from_iter([( - class_hash, - CompiledClassHash(compiled_class_hash.try_into()?), - )]), - ..Default::default() - }), - None => Ok(ThinStateDiff { - deprecated_declared_classes: vec![class_hash], - ..Default::default() - }), - } - } -} - -impl TryFrom for (StorageKey, StarkFelt) { - type Error = ProtobufConversionError; - fn try_from(entry: protobuf::ContractStoredValue) -> Result { - let key_felt = - StarkFelt::try_from(entry.key.ok_or(ProtobufConversionError::MissingField { - field_description: "ContractStoredValue::key", - })?)?; - let key = StorageKey(key_felt.try_into().map_err(|_| { - ProtobufConversionError::OutOfRangeValue { - // TODO(shahak): Check if the type in the protobuf of the field - // ContractStoredValue::key should be changed into a PatriciaKey which has a - // slightly lower bound than Felt. - type_description: "Felt252", - value_as_str: format!("{key_felt:?}"), - } - })?); - let value = - StarkFelt::try_from(entry.value.ok_or(ProtobufConversionError::MissingField { - field_description: "ContractStoredValue::value", - })?)?; - Ok((key, value)) - } -} - -// A wrapper struct for Vec so that we can implement traits for it. -pub struct StateDiffsResponseVec(pub Vec); - -const DOMAIN: u32 = 0; - -impl From for StateDiffsResponseVec { - fn from(value: ThinStateDiff) -> Self { - let mut result = Vec::new(); - - for (contract_address, class_hash) in - value.deployed_contracts.into_iter().chain(value.replaced_classes.into_iter()) - { - result.push(protobuf::StateDiffsResponse { - state_diff_message: Some( - protobuf::state_diffs_response::StateDiffMessage::ContractDiff( - protobuf::ContractDiff { - address: Some(contract_address.into()), - class_hash: Some(class_hash.0.into()), - domain: DOMAIN, - ..Default::default() - }, - ), - ), - }); - } - for (contract_address, storage_diffs) in value.storage_diffs { - if storage_diffs.is_empty() { - continue; - } - result.push(protobuf::StateDiffsResponse { - state_diff_message: Some( - protobuf::state_diffs_response::StateDiffMessage::ContractDiff( - protobuf::ContractDiff { - address: Some(contract_address.into()), - values: storage_diffs - .into_iter() - .map(|(key, value)| protobuf::ContractStoredValue { - key: Some((*key.0.key()).into()), - value: Some(value.into()), - }) - .collect(), - domain: DOMAIN, - ..Default::default() - }, - ), - ), - }); - } - for (contract_address, nonce) in value.nonces { - result.push(protobuf::StateDiffsResponse { - state_diff_message: Some( - protobuf::state_diffs_response::StateDiffMessage::ContractDiff( - protobuf::ContractDiff { - address: Some(contract_address.into()), - nonce: Some(nonce.0.into()), - domain: DOMAIN, - ..Default::default() - }, - ), - ), - }); - } - - for (class_hash, compiled_class_hash) in value.declared_classes { - result.push(protobuf::StateDiffsResponse { - state_diff_message: Some( - protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( - protobuf::DeclaredClass { - class_hash: Some(class_hash.0.into()), - compiled_class_hash: Some(compiled_class_hash.0.into()), - }, - ), - ), - }); - } - for class_hash in value.deprecated_declared_classes { - result.push(protobuf::StateDiffsResponse { - state_diff_message: Some( - protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( - protobuf::DeclaredClass { - class_hash: Some(class_hash.0.into()), - compiled_class_hash: None, - }, - ), - ), - }); - } - - Self(result) - } -} - -impl TryFrom for InternalQuery { - type Error = ProtobufConversionError; - fn try_from(value: protobuf::StateDiffsRequest) -> Result { - let value = value.iteration.ok_or(ProtobufConversionError::MissingField { - field_description: "StateDiffsRequest::iteration", - })?; - value.try_into() - } -} - -impl From for protobuf::StateDiffsRequest { - fn from(value: Query) -> Self { - protobuf::StateDiffsRequest { iteration: Some(value.into()) } - } -} diff --git a/crates/papyrus_network/src/converters/test.rs b/crates/papyrus_network/src/converters/test.rs deleted file mode 100644 index ece0877df9d..00000000000 --- a/crates/papyrus_network/src/converters/test.rs +++ /dev/null @@ -1,26 +0,0 @@ -use prost::Message; -use starknet_api::block::BlockHeader; - -use crate::db_executor::Data; -use crate::protobuf_messages::protobuf; - -#[test] -fn block_header_to_protobuf_to_bytes_and_back() { - let data = Data::BlockHeaderAndSignature { - // TODO(shahak): Remove state_diff_length from here once we correctly deduce if it should - // be None or Some. - header: BlockHeader { state_diff_length: Some(0), ..Default::default() }, - signatures: vec![], - }; - dbg!(&data); - let mut data_bytes: Vec = vec![]; - >::try_into(data.clone()) - .expect( - "Data::BlockHeaderAndSignature should be convertable to protobuf::BlockHeadersResponse", - ) - .encode(&mut data_bytes) - .unwrap(); - let res_data: Data = - protobuf::BlockHeadersResponse::decode(&data_bytes[..]).unwrap().try_into().unwrap(); - assert_eq!(res_data, data); -} diff --git a/crates/papyrus_network/src/db_executor/mod.rs b/crates/papyrus_network/src/db_executor/mod.rs index 511664cf95d..fb26461662c 100644 --- a/crates/papyrus_network/src/db_executor/mod.rs +++ b/crates/papyrus_network/src/db_executor/mod.rs @@ -10,17 +10,19 @@ use futures::stream::FuturesUnordered; use futures::{Stream, StreamExt}; #[cfg(test)] use mockall::automock; +use papyrus_protobuf::converters::common::volition_domain_to_enum_int; +use papyrus_protobuf::converters::state_diff::DOMAIN; +use papyrus_protobuf::protobuf; +use papyrus_protobuf::sync::{BlockHashOrNumber, Query, SignedBlockHeader}; use papyrus_storage::header::HeaderStorageReader; use papyrus_storage::state::StateStorageReader; use papyrus_storage::{db, StorageReader, StorageTxn}; use prost::Message; -use starknet_api::block::{BlockHeader, BlockNumber, BlockSignature}; +use starknet_api::block::BlockNumber; use starknet_api::state::ThinStateDiff; use tokio::task::JoinHandle; -use crate::converters::protobuf_conversion::state_diff::StateDiffsResponseVec; -use crate::protobuf_messages::protobuf; -use crate::{BlockHashOrNumber, DataType, InternalQuery}; +use crate::DataType; #[cfg(test)] mod test; @@ -34,10 +36,10 @@ pub struct QueryId(pub usize); #[error("Failed to encode data")] pub struct DataEncodingError; -#[cfg_attr(test, derive(Debug, Clone, PartialEq, Eq))] +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Clone)] pub enum Data { - // TODO(shahak): Consider uniting with SignedBlockHeader. - BlockHeaderAndSignature { header: BlockHeader, signatures: Vec }, + BlockHeaderAndSignature(SignedBlockHeader), StateDiff { state_diff: ThinStateDiff }, Fin(DataType), } @@ -59,13 +61,13 @@ impl Data { B: BufMut, { match self { - Data::BlockHeaderAndSignature { .. } => self - .try_into() - .map(|data: protobuf::BlockHeadersResponse| match encode_with_length_prefix_flag { + Data::BlockHeaderAndSignature(signed_block_header) => { + let data: protobuf::BlockHeadersResponse = Some(signed_block_header).into(); + match encode_with_length_prefix_flag { true => data.encode_length_delimited(buf).map_err(|_| DataEncodingError), false => data.encode(buf).map_err(|_| DataEncodingError), - }) - .map_err(|_| DataEncodingError)?, + } + } Data::StateDiff { state_diff } => { let state_diffs_response_vec = Into::::into(state_diff); let res = state_diffs_response_vec @@ -139,7 +141,7 @@ pub enum DBExecutorError { #[error( "Block number is out of range. Query: {query:?}, counter: {counter}, query_id: {query_id}" )] - BlockNumberOutOfRange { query: InternalQuery, counter: u64, query_id: QueryId }, + BlockNumberOutOfRange { query: Query, counter: u64, query_id: QueryId }, // TODO: add data type to the error message. #[error("Block not found. Block: {block_hash_or_number:?}, query_id: {query_id}")] BlockNotFound { block_hash_or_number: BlockHashOrNumber, query_id: QueryId }, @@ -182,38 +184,38 @@ impl DBExecutorError { } } -/// Db executor is a stream of queries. Each result is marks the end of a query fulfillment. +/// DBExecutorTrait is a stream of queries. Each result is marks the end of a query fulfillment. /// A query can either succeed (and return Ok(QueryId)) or fail (and return Err(DBExecutorError)). /// The stream is never exhausted, and it is the responsibility of the user to poll it. -pub trait DBExecutor: Stream> + Unpin { +pub trait DBExecutorTrait: Stream> + Unpin { // TODO: add writer functionality fn register_query( &mut self, - query: InternalQuery, + query: Query, data_type: impl FetchBlockDataFromDb + Send + 'static, - sender: Sender, + sender: Sender>, ) -> QueryId; } // TODO: currently this executor returns only block headers and signatures. -pub struct BlockHeaderDBExecutor { +pub struct DBExecutor { next_query_id: usize, storage_reader: StorageReader, query_execution_set: FuturesUnordered>>, } -impl BlockHeaderDBExecutor { +impl DBExecutor { pub fn new(storage_reader: StorageReader) -> Self { Self { next_query_id: 0, storage_reader, query_execution_set: FuturesUnordered::new() } } } -impl DBExecutor for BlockHeaderDBExecutor { +impl DBExecutorTrait for DBExecutor { fn register_query( &mut self, - query: InternalQuery, + query: Query, data_type: impl FetchBlockDataFromDb + Send + 'static, - mut sender: Sender, + mut sender: Sender>, ) -> QueryId { let query_id = QueryId(self.next_query_id); self.next_query_id += 1; @@ -240,16 +242,17 @@ impl DBExecutor for BlockHeaderDBExecutor { }; for block_counter in 0..query.limit { let block_number = BlockNumber(utils::calculate_block_number( - query, + &query, start_block_number, block_counter, query_id, )?); - let data = data_type.fetch_block_data_from_db(block_number, query_id, &txn)?; + let data_vec = + data_type.fetch_block_data_from_db(block_number, query_id, &txn)?; // Using poll_fn because Sender::poll_ready is not a future match poll_fn(|cx| sender.poll_ready(cx)).await { Ok(()) => { - if let Err(e) = sender.start_send(data) { + if let Err(e) = sender.start_send(data_vec) { // TODO: consider implement retry mechanism. return Err(DBExecutorError::SendError { query_id, send_error: e }); }; @@ -266,7 +269,7 @@ impl DBExecutor for BlockHeaderDBExecutor { } } -impl Stream for BlockHeaderDBExecutor { +impl Stream for DBExecutor { type Item = Result; fn poll_next( @@ -304,7 +307,7 @@ pub trait FetchBlockDataFromDb { block_number: BlockNumber, query_id: QueryId, txn: &StorageTxn<'a, db::RO>, - ) -> Result; + ) -> Result, DBExecutorError>; } impl FetchBlockDataFromDb for DataType { @@ -313,7 +316,7 @@ impl FetchBlockDataFromDb for DataType { block_number: BlockNumber, query_id: QueryId, txn: &StorageTxn<'_, db::RO>, - ) -> Result { + ) -> Result, DBExecutorError> { match self { DataType::SignedBlockHeader => { let mut header = txn @@ -348,21 +351,116 @@ impl FetchBlockDataFromDb for DataType { storage_error: err, })? .ok_or(DBExecutorError::SignatureNotFound { block_number, query_id })?; - Ok(Data::BlockHeaderAndSignature { header, signatures: vec![signature] }) + Ok(vec![Data::BlockHeaderAndSignature(SignedBlockHeader { + block_header: header, + signatures: vec![signature], + })]) } DataType::StateDiff => { - let state_diff = txn - .get_state_diff(block_number) - .map_err(|err| DBExecutorError::DBInternalError { - query_id, - storage_error: err, - })? - .ok_or(DBExecutorError::BlockNotFound { - block_hash_or_number: BlockHashOrNumber::Number(block_number), - query_id, - })?; - Ok(Data::StateDiff { state_diff }) + let vec_data = vec![Data::StateDiff { + state_diff: txn + .get_state_diff(block_number) + .map_err(|err| DBExecutorError::DBInternalError { + query_id, + storage_error: err, + })? + .ok_or(DBExecutorError::BlockNotFound { + block_hash_or_number: BlockHashOrNumber::Number(block_number), + query_id, + })?, + }]; + Ok(vec_data) } } } } + +// A wrapper struct for Vec so that we can implement traits for it. +pub struct StateDiffsResponseVec(pub Vec); + +impl From for StateDiffsResponseVec { + fn from(value: ThinStateDiff) -> Self { + let mut result = Vec::new(); + + for (contract_address, class_hash) in + value.deployed_contracts.into_iter().chain(value.replaced_classes.into_iter()) + { + result.push(protobuf::StateDiffsResponse { + state_diff_message: Some( + protobuf::state_diffs_response::StateDiffMessage::ContractDiff( + protobuf::ContractDiff { + address: Some(contract_address.into()), + class_hash: Some(class_hash.0.into()), + domain: volition_domain_to_enum_int(DOMAIN), + ..Default::default() + }, + ), + ), + }); + } + for (contract_address, storage_diffs) in value.storage_diffs { + if storage_diffs.is_empty() { + continue; + } + result.push(protobuf::StateDiffsResponse { + state_diff_message: Some( + protobuf::state_diffs_response::StateDiffMessage::ContractDiff( + protobuf::ContractDiff { + address: Some(contract_address.into()), + values: storage_diffs + .into_iter() + .map(|(key, value)| protobuf::ContractStoredValue { + key: Some((*key.0.key()).into()), + value: Some(value.into()), + }) + .collect(), + domain: volition_domain_to_enum_int(DOMAIN), + ..Default::default() + }, + ), + ), + }); + } + for (contract_address, nonce) in value.nonces { + result.push(protobuf::StateDiffsResponse { + state_diff_message: Some( + protobuf::state_diffs_response::StateDiffMessage::ContractDiff( + protobuf::ContractDiff { + address: Some(contract_address.into()), + nonce: Some(nonce.0.into()), + domain: volition_domain_to_enum_int(DOMAIN), + ..Default::default() + }, + ), + ), + }); + } + + for (class_hash, compiled_class_hash) in value.declared_classes { + result.push(protobuf::StateDiffsResponse { + state_diff_message: Some( + protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + protobuf::DeclaredClass { + class_hash: Some(class_hash.0.into()), + compiled_class_hash: Some(compiled_class_hash.0.into()), + }, + ), + ), + }); + } + for class_hash in value.deprecated_declared_classes { + result.push(protobuf::StateDiffsResponse { + state_diff_message: Some( + protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + protobuf::DeclaredClass { + class_hash: Some(class_hash.0.into()), + compiled_class_hash: None, + }, + ), + ), + }); + } + + Self(result) + } +} diff --git a/crates/papyrus_network/src/db_executor/test.rs b/crates/papyrus_network/src/db_executor/test.rs index e0f584fb219..5060bdefb5f 100644 --- a/crates/papyrus_network/src/db_executor/test.rs +++ b/crates/papyrus_network/src/db_executor/test.rs @@ -5,6 +5,7 @@ use futures::channel::mpsc::Receiver; use futures::future::poll_fn; use futures::stream::SelectAll; use futures::{FutureExt, StreamExt}; +use papyrus_protobuf::sync::{BlockHashOrNumber, Direction, Query, SignedBlockHeader}; use papyrus_storage::header::{HeaderStorageReader, HeaderStorageWriter}; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::test_utils::get_test_storage; @@ -14,31 +15,39 @@ use starknet_api::block::{BlockHash, BlockHeader, BlockNumber, BlockSignature}; use starknet_api::state::ThinStateDiff; use super::Data::BlockHeaderAndSignature; -use crate::db_executor::{DBExecutor, DBExecutorError, Data, MockFetchBlockDataFromDb, QueryId}; -use crate::{BlockHashOrNumber, DataType, Direction, InternalQuery}; +use crate::db_executor::{ + DBExecutorError, + DBExecutorTrait, + Data, + MockFetchBlockDataFromDb, + QueryId, +}; +use crate::DataType; + const BUFFER_SIZE: usize = 10; #[tokio::test] async fn header_db_executor_can_register_and_run_a_query() { let ((storage_reader, mut storage_writer), _temp_dir) = get_test_storage(); - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); // put some data in the storage. const NUM_OF_BLOCKS: u64 = 10; insert_to_storage_test_blocks_up_to(NUM_OF_BLOCKS, &mut storage_writer); // register a query. - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(0)), direction: Direction::Forward, limit: NUM_OF_BLOCKS, step: 1, }; - let (query_ids, mut receivers): (Vec, Vec<(Receiver, DataType)>) = + type ReceiversType = Vec<(Receiver>, DataType)>; + let (query_ids, mut receivers): (Vec, ReceiversType) = enum_iterator::all::() .map(|data_type| { let (sender, receiver) = futures::channel::mpsc::channel(BUFFER_SIZE); - let query_id = db_executor.register_query(query, data_type, sender); + let query_id = db_executor.register_query(query.clone(), data_type, sender); (query_id, (receiver, data_type)) }) .unzip(); @@ -65,27 +74,19 @@ async fn header_db_executor_can_register_and_run_a_query() { let (data, requested_data_type) = res.await; assert_eq!(data.len(), NUM_OF_BLOCKS as usize); for (i, data) in data.iter().enumerate() { - if i == 0{ - // requested DataType dictates what kind of Data we should expect. - match requested_data_type { - DataType::SignedBlockHeader => { - assert_matches!(data, BlockHeaderAndSignature { .. }); + for data in data.iter() { + match data { + Data::BlockHeaderAndSignature(SignedBlockHeader{block_header: BlockHeader { block_number: BlockNumber(block_number), .. }, ..}) => { + assert_eq!(block_number, &(i as u64)); + assert_eq!(*requested_data_type,DataType::SignedBlockHeader); } - DataType::StateDiff => { - assert_matches!(data, Data::StateDiff{..}); - + Data::StateDiff{state_diff: ThinStateDiff { .. }} => { + // TODO: check the state diff. + assert_eq!(*requested_data_type,DataType::StateDiff); } + _ => panic!("Unexpected data type"), } } - match data { - Data::BlockHeaderAndSignature { header: BlockHeader { block_number: BlockNumber(block_number), .. }, ..} => { - assert_eq!(block_number, &(i as u64)); - } - Data::StateDiff{state_diff: ThinStateDiff { .. }} => { - // TODO: check the state diff. - } - _ => panic!("Unexpected data type"), - } } } } @@ -107,11 +108,11 @@ async fn header_db_executor_start_block_given_by_hash() { .unwrap() .block_hash; - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); // register a query. let (sender, receiver) = futures::channel::mpsc::channel(BUFFER_SIZE); - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Hash(block_hash), direction: Direction::Forward, limit: NUM_OF_BLOCKS, @@ -129,7 +130,7 @@ async fn header_db_executor_start_block_given_by_hash() { res = receiver.collect::>() => { assert_eq!(res.len(), NUM_OF_BLOCKS as usize); for (i, data) in res.iter().enumerate() { - assert_matches!(data, BlockHeaderAndSignature { header: BlockHeader { block_number: BlockNumber(block_number), .. }, ..} if block_number == &(i as u64)); + assert_matches!(data.first().unwrap(), BlockHeaderAndSignature(SignedBlockHeader{block_header: BlockHeader { block_number: BlockNumber(block_number), .. }, ..}) if block_number == &(i as u64)); } } } @@ -137,7 +138,7 @@ async fn header_db_executor_start_block_given_by_hash() { #[tokio::test] async fn header_db_executor_query_of_missing_block() { let ((storage_reader, mut storage_writer), _temp_dir) = get_test_storage(); - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); const NUM_OF_BLOCKS: u64 = 15; insert_to_storage_test_blocks_up_to(NUM_OF_BLOCKS, &mut storage_writer); @@ -145,7 +146,7 @@ async fn header_db_executor_query_of_missing_block() { const BLOCKS_DELTA: u64 = 5; // register a query. let (sender, receiver) = futures::channel::mpsc::channel(BUFFER_SIZE); - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(NUM_OF_BLOCKS - BLOCKS_DELTA)), direction: Direction::Forward, limit: NUM_OF_BLOCKS, @@ -160,7 +161,7 @@ async fn header_db_executor_query_of_missing_block() { query_id, }) } else { - Ok(Data::default()) + Ok(vec![Data::default()]) } }, ); @@ -180,7 +181,7 @@ async fn header_db_executor_query_of_missing_block() { #[test] fn header_db_executor_stream_pending_with_no_query() { let ((storage_reader, _), _temp_dir) = get_test_storage(); - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); // poll without registering a query. assert!(poll_fn(|cx| db_executor.poll_next_unpin(cx)).now_or_never().is_none()); @@ -189,7 +190,7 @@ fn header_db_executor_stream_pending_with_no_query() { #[tokio::test] async fn header_db_executor_can_receive_queries_after_stream_is_exhausted() { let ((storage_reader, mut storage_writer), _temp_dir) = get_test_storage(); - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); const NUM_OF_BLOCKS: u64 = 10; insert_to_storage_test_blocks_up_to(NUM_OF_BLOCKS, &mut storage_writer); @@ -197,7 +198,7 @@ async fn header_db_executor_can_receive_queries_after_stream_is_exhausted() { for _ in 0..2 { // register a query. let (sender, receiver) = futures::channel::mpsc::channel(BUFFER_SIZE); - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(0)), direction: Direction::Forward, limit: NUM_OF_BLOCKS, @@ -207,7 +208,7 @@ async fn header_db_executor_can_receive_queries_after_stream_is_exhausted() { mock_data_type .expect_fetch_block_data_from_db() .times(NUM_OF_BLOCKS as usize) - .returning(|_, _, _| Ok(Data::default())); + .returning(|_, _, _| Ok(vec![Data::default()])); let query_id = db_executor.register_query(query, mock_data_type, sender); // run the executor and collect query results. @@ -228,13 +229,13 @@ async fn header_db_executor_can_receive_queries_after_stream_is_exhausted() { #[tokio::test] async fn header_db_executor_drop_receiver_before_query_is_done() { let ((storage_reader, mut storage_writer), _temp_dir) = get_test_storage(); - let mut db_executor = super::BlockHeaderDBExecutor::new(storage_reader); + let mut db_executor = super::DBExecutor::new(storage_reader); const NUM_OF_BLOCKS: u64 = 10; insert_to_storage_test_blocks_up_to(NUM_OF_BLOCKS, &mut storage_writer); let (sender, receiver) = futures::channel::mpsc::channel(BUFFER_SIZE); - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(1)), direction: Direction::Forward, limit: NUM_OF_BLOCKS, diff --git a/crates/papyrus_network/src/db_executor/utils.rs b/crates/papyrus_network/src/db_executor/utils.rs index fc7d05560e3..f87df61a7fb 100644 --- a/crates/papyrus_network/src/db_executor/utils.rs +++ b/crates/papyrus_network/src/db_executor/utils.rs @@ -1,8 +1,9 @@ +use papyrus_protobuf::sync::{Direction, Query}; + use super::{DBExecutorError, QueryId}; -use crate::{Direction, InternalQuery}; pub(crate) fn calculate_block_number( - query: InternalQuery, + query: &Query, start_block: u64, read_blocks_counter: u64, query_id: QueryId, @@ -11,11 +12,12 @@ pub(crate) fn calculate_block_number( Direction::Forward => 1, Direction::Backward => -1, }; + // TODO(shahak): Fix this code. let blocks_delta: i128 = direction_factor * (query.step * read_blocks_counter) as i128; let block_number: i128 = start_block as i128 + blocks_delta; if block_number < 0 || block_number > u64::MAX as i128 { return Err(DBExecutorError::BlockNumberOutOfRange { - query, + query: query.clone(), counter: read_blocks_counter, query_id, }); diff --git a/crates/papyrus_network/src/discovery/discovery_test.rs b/crates/papyrus_network/src/discovery/discovery_test.rs index 9dde3f022ce..d6149771ee4 100644 --- a/crates/papyrus_network/src/discovery/discovery_test.rs +++ b/crates/papyrus_network/src/discovery/discovery_test.rs @@ -24,11 +24,11 @@ use tokio::sync::Mutex; use tokio::time::timeout; use void::Void; -use super::kad_impl::KadFromOtherBehaviourEvent; -use super::{Behaviour, FromOtherBehaviourEvent, ToOtherBehaviourEvent}; -use crate::mixed_behaviour; +use super::kad_impl::KadToOtherBehaviourEvent; +use super::{Behaviour, ToOtherBehaviourEvent, DIAL_SLEEP}; use crate::mixed_behaviour::BridgedBehaviour; use crate::test_utils::next_on_mutex_stream; +use crate::{mixed_behaviour, peer_manager}; const TIMEOUT: Duration = Duration::from_secs(1); const SLEEP_DURATION: Duration = Duration::from_millis(10); @@ -73,6 +73,8 @@ async fn discovery_outputs_dial_request_on_start_without_query() { #[tokio::test] async fn discovery_redials_on_dial_failure() { + const EPSILON_SLEEP: Duration = Duration::from_millis(10); + let bootstrap_peer_id = PeerId::random(); let bootstrap_peer_address = Multiaddr::empty(); @@ -90,6 +92,14 @@ async fn discovery_redials_on_dial_failure() { connection_id: ConnectionId::new_unchecked(0), })); + // Check that there are no events until we sleep for enough time. + tokio::time::pause(); + tokio::time::advance(DIAL_SLEEP - EPSILON_SLEEP).await; + assert_no_event(&mut behaviour); + + // Sleep and check for event. + tokio::time::advance(EPSILON_SLEEP).await; + tokio::time::resume(); let event = timeout(TIMEOUT, behaviour.next()).await.unwrap().unwrap(); assert_matches!( event, @@ -177,12 +187,11 @@ async fn create_behaviour_and_connect_to_bootstrap_node() -> Behaviour { let event = timeout(TIMEOUT, behaviour.next()).await.unwrap().unwrap(); assert_matches!( event, - ToSwarm::GenerateEvent(ToOtherBehaviourEvent( - KadFromOtherBehaviourEvent::FoundListenAddresses { + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::FoundListenAddresses { peer_id, listen_addresses, } - )) if peer_id == bootstrap_peer_id && listen_addresses == vec![bootstrap_peer_address] + ) if peer_id == bootstrap_peer_id && listen_addresses == vec![bootstrap_peer_address] ); behaviour @@ -195,9 +204,7 @@ async fn discovery_outputs_single_query_after_connecting() { let event = timeout(TIMEOUT, behaviour.next()).await.unwrap().unwrap(); assert_matches!( event, - ToSwarm::GenerateEvent(ToOtherBehaviourEvent(KadFromOtherBehaviourEvent::RequestKadQuery( - _peer_id - ))) + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::RequestKadQuery(_peer_id)) ); assert_no_event(&mut behaviour); @@ -207,20 +214,18 @@ async fn discovery_outputs_single_query_after_connecting() { async fn discovery_doesnt_output_queries_while_paused() { let mut behaviour = create_behaviour_and_connect_to_bootstrap_node().await; - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::PauseDiscovery, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::PauseDiscovery, )); assert_no_event(&mut behaviour); - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::ResumeDiscovery, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::ResumeDiscovery, )); let event = timeout(TIMEOUT, behaviour.next()).await.unwrap().unwrap(); assert_matches!( event, - ToSwarm::GenerateEvent(ToOtherBehaviourEvent(KadFromOtherBehaviourEvent::RequestKadQuery( - _peer_id - ))) + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::RequestKadQuery(_peer_id)) ); } @@ -231,15 +236,13 @@ async fn discovery_outputs_single_query_on_query_finished() { // Consume the initial query event. timeout(TIMEOUT, behaviour.next()).await.unwrap(); - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::KadQueryFinished, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::Kad( + KadToOtherBehaviourEvent::KadQueryFinished, )); let event = timeout(TIMEOUT, behaviour.next()).await.unwrap().unwrap(); assert_matches!( event, - ToSwarm::GenerateEvent(ToOtherBehaviourEvent(KadFromOtherBehaviourEvent::RequestKadQuery( - _peer_id - ))) + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::RequestKadQuery(_peer_id)) ); } @@ -250,14 +253,14 @@ async fn discovery_doesnt_output_queries_if_query_finished_while_paused() { // Consume the initial query event. timeout(TIMEOUT, behaviour.next()).await.unwrap(); - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::PauseDiscovery, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::PauseDiscovery, )); assert_no_event(&mut behaviour); // Simulate that the query has finished. - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::KadQueryFinished, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::Kad( + KadToOtherBehaviourEvent::KadQueryFinished, )); assert_no_event(&mut behaviour); } @@ -266,8 +269,8 @@ async fn discovery_doesnt_output_queries_if_query_finished_while_paused() { async fn discovery_awakes_on_resume() { let mut behaviour = create_behaviour_and_connect_to_bootstrap_node().await; - behaviour.on_other_behaviour_event(mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::PauseDiscovery, + behaviour.on_other_behaviour_event(&mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::PauseDiscovery, )); // There should be an event once we resume because discovery has just started. @@ -278,8 +281,8 @@ async fn discovery_awakes_on_resume() { _ = async { tokio::time::sleep(SLEEP_DURATION).await; mutex.lock().await.on_other_behaviour_event( - mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::ResumeDiscovery, + &mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::ResumeDiscovery, ) ); timeout(TIMEOUT, pending::<()>()).await.unwrap(); @@ -301,9 +304,8 @@ async fn discovery_awakes_on_query_finished() { _ = async { tokio::time::sleep(SLEEP_DURATION).await; mutex.lock().await.on_other_behaviour_event( - mixed_behaviour::InternalEvent::NotifyDiscovery( - FromOtherBehaviourEvent::KadQueryFinished, - + &mixed_behaviour::ToOtherBehaviourEvent::Kad( + KadToOtherBehaviourEvent::KadQueryFinished, ) ); timeout(TIMEOUT, pending::<()>()).await.unwrap(); diff --git a/crates/papyrus_network/src/discovery/flow_test.rs b/crates/papyrus_network/src/discovery/flow_test.rs index 4fd759dc6d5..43f01ca7c1f 100644 --- a/crates/papyrus_network/src/discovery/flow_test.rs +++ b/crates/papyrus_network/src/discovery/flow_test.rs @@ -3,7 +3,7 @@ use std::iter; use futures::StreamExt; use libp2p::core::multiaddr::Protocol; -use libp2p::identity::PublicKey; +use libp2p::identity::Keypair; use libp2p::kad::store::MemoryStore; use libp2p::swarm::behaviour::toggle::Toggle; use libp2p::swarm::{NetworkBehaviour, SwarmEvent}; @@ -13,7 +13,7 @@ use libp2p_swarm_test::SwarmExt; use super::Behaviour; use crate::mixed_behaviour; use crate::mixed_behaviour::{BridgedBehaviour, MixedBehaviour}; -use crate::test_utils::StreamHashMap; +use crate::utils::StreamHashMap; #[derive(NetworkBehaviour)] struct DiscoveryMixedBehaviour { @@ -23,7 +23,7 @@ struct DiscoveryMixedBehaviour { } impl DiscoveryMixedBehaviour { - pub fn new(key: PublicKey, bootstrap_peer_multiaddr: Option) -> Self { + pub fn new(key: Keypair, bootstrap_peer_multiaddr: Option) -> Self { let mixed_behaviour = MixedBehaviour::new(key, bootstrap_peer_multiaddr, Default::default()); Self { @@ -39,7 +39,7 @@ async fn all_nodes_have_same_bootstrap_peer() { const NUM_NODES: usize = 2; let mut bootstrap_swarm = - Swarm::new_ephemeral(|keypair| DiscoveryMixedBehaviour::new(keypair.public(), None)); + Swarm::new_ephemeral(|keypair| DiscoveryMixedBehaviour::new(keypair, None)); bootstrap_swarm.listen().with_memory_addr_external().await; let bootstrap_peer_id = *bootstrap_swarm.local_peer_id(); @@ -53,7 +53,7 @@ async fn all_nodes_have_same_bootstrap_peer() { let swarms = (0..NUM_NODES).map(|_| { Swarm::new_ephemeral(|keypair| { - DiscoveryMixedBehaviour::new(keypair.public(), Some(bootstrap_peer_multiaddr.clone())) + DiscoveryMixedBehaviour::new(keypair, Some(bootstrap_peer_multiaddr.clone())) }) }); let mut swarms_stream = StreamHashMap::new( @@ -85,21 +85,17 @@ async fn all_nodes_have_same_bootstrap_peer() { _ => continue, }; - let mixed_behaviour::Event::InternalEvent(event) = mixed_event else { + let mixed_behaviour::Event::ToOtherBehaviourEvent(event) = mixed_event else { + continue; + }; + if let mixed_behaviour::ToOtherBehaviourEvent::NoOp = event { continue; }; let behaviour_ref = swarms_stream.get_mut(&peer_id).unwrap().behaviour_mut(); - match event { - mixed_behaviour::InternalEvent::NoOp => {} - mixed_behaviour::InternalEvent::NotifyKad(_) => { - behaviour_ref.kademlia.on_other_behaviour_event(event) - } - mixed_behaviour::InternalEvent::NotifyDiscovery(_) => { - if let Some(discovery) = behaviour_ref.discovery.as_mut() { - discovery.on_other_behaviour_event(event); - } - } - _ => {} + behaviour_ref.identify.on_other_behaviour_event(&event); + behaviour_ref.kademlia.on_other_behaviour_event(&event); + if let Some(discovery) = behaviour_ref.discovery.as_mut() { + discovery.on_other_behaviour_event(&event); } } } diff --git a/crates/papyrus_network/src/discovery/identify_impl.rs b/crates/papyrus_network/src/discovery/identify_impl.rs index 0ed2e80ae83..85b342382b1 100644 --- a/crates/papyrus_network/src/discovery/identify_impl.rs +++ b/crates/papyrus_network/src/discovery/identify_impl.rs @@ -1,23 +1,36 @@ -use libp2p::identify; +use libp2p::{identify, Multiaddr, PeerId}; -use super::kad_impl::KadFromOtherBehaviourEvent; use crate::mixed_behaviour; +use crate::mixed_behaviour::BridgedBehaviour; pub const IDENTIFY_PROTOCOL_VERSION: &str = "/staknet/identify/0.1.0-rc.0"; +#[derive(Debug)] +pub enum IdentifyToOtherBehaviourEvent { + FoundListenAddresses { peer_id: PeerId, listen_addresses: Vec }, +} + impl From for mixed_behaviour::Event { fn from(event: identify::Event) -> Self { match event { identify::Event::Received { peer_id, info } => { - mixed_behaviour::Event::InternalEvent(mixed_behaviour::InternalEvent::NotifyKad( - KadFromOtherBehaviourEvent::FoundListenAddresses { - peer_id, - listen_addresses: info.listen_addrs, - }, - )) + mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::Identify( + IdentifyToOtherBehaviourEvent::FoundListenAddresses { + peer_id, + listen_addresses: info.listen_addrs, + }, + ), + ) } // TODO(shahak): Consider logging error events. - _ => mixed_behaviour::Event::InternalEvent(mixed_behaviour::InternalEvent::NoOp), + _ => mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::NoOp, + ), } } } + +impl BridgedBehaviour for identify::Behaviour { + fn on_other_behaviour_event(&mut self, _event: &mixed_behaviour::ToOtherBehaviourEvent) {} +} diff --git a/crates/papyrus_network/src/discovery/kad_impl.rs b/crates/papyrus_network/src/discovery/kad_impl.rs index 60254a56ad5..cfdd0bd9455 100644 --- a/crates/papyrus_network/src/discovery/kad_impl.rs +++ b/crates/papyrus_network/src/discovery/kad_impl.rs @@ -1,13 +1,13 @@ -use libp2p::{kad, Multiaddr, PeerId}; +use libp2p::kad; use tracing::error; +use super::identify_impl::IdentifyToOtherBehaviourEvent; +use crate::mixed_behaviour; use crate::mixed_behaviour::BridgedBehaviour; -use crate::{discovery, mixed_behaviour}; #[derive(Debug)] -pub enum KadFromOtherBehaviourEvent { - RequestKadQuery(PeerId), - FoundListenAddresses { peer_id: PeerId, listen_addresses: Vec }, +pub enum KadToOtherBehaviourEvent { + KadQueryFinished, } impl From for mixed_behaviour::Event { @@ -21,31 +21,38 @@ impl From for mixed_behaviour::Event { if let Err(err) = result { error!("Kademlia query failed on {err:?}"); } - mixed_behaviour::Event::InternalEvent( - mixed_behaviour::InternalEvent::NotifyDiscovery( - discovery::FromOtherBehaviourEvent::KadQueryFinished, + mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::Kad( + KadToOtherBehaviourEvent::KadQueryFinished, ), ) } - _ => mixed_behaviour::Event::InternalEvent(mixed_behaviour::InternalEvent::NoOp), + _ => mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::NoOp, + ), } } } impl BridgedBehaviour for kad::Behaviour { - fn on_other_behaviour_event(&mut self, event: mixed_behaviour::InternalEvent) { - let mixed_behaviour::InternalEvent::NotifyKad(event) = event else { - return; - }; + fn on_other_behaviour_event(&mut self, event: &mixed_behaviour::ToOtherBehaviourEvent) { match event { - KadFromOtherBehaviourEvent::RequestKadQuery(peer_id) => { - self.get_closest_peers(peer_id); + mixed_behaviour::ToOtherBehaviourEvent::Discovery( + super::ToOtherBehaviourEvent::RequestKadQuery(peer_id), + ) => { + self.get_closest_peers(*peer_id); } - KadFromOtherBehaviourEvent::FoundListenAddresses { peer_id, listen_addresses } => { + mixed_behaviour::ToOtherBehaviourEvent::Identify( + IdentifyToOtherBehaviourEvent::FoundListenAddresses { peer_id, listen_addresses }, + ) + | mixed_behaviour::ToOtherBehaviourEvent::Discovery( + super::ToOtherBehaviourEvent::FoundListenAddresses { peer_id, listen_addresses }, + ) => { for address in listen_addresses { - self.add_address(&peer_id, address); + self.add_address(peer_id, address.clone()); } } + _ => {} } } } diff --git a/crates/papyrus_network/src/discovery/mod.rs b/crates/papyrus_network/src/discovery/mod.rs index 3c452daf125..48ac5c2b8c5 100644 --- a/crates/papyrus_network/src/discovery/mod.rs +++ b/crates/papyrus_network/src/discovery/mod.rs @@ -5,9 +5,12 @@ mod flow_test; pub mod identify_impl; pub mod kad_impl; -use std::task::{Context, Poll, Waker}; +use std::task::{ready, Context, Poll, Waker}; +use std::time::Duration; -use kad_impl::KadFromOtherBehaviourEvent; +use futures::future::BoxFuture; +use futures::{pin_mut, Future, FutureExt}; +use kad_impl::KadToOtherBehaviourEvent; use libp2p::core::Endpoint; use libp2p::swarm::behaviour::ConnectionEstablished; use libp2p::swarm::dial_opts::{DialOpts, PeerCondition}; @@ -25,8 +28,11 @@ use libp2p::swarm::{ }; use libp2p::{Multiaddr, PeerId}; -use crate::mixed_behaviour; use crate::mixed_behaviour::BridgedBehaviour; +use crate::{mixed_behaviour, peer_manager}; + +// TODO(shahak): Consider adding to config. +const DIAL_SLEEP: Duration = Duration::from_secs(5); pub struct Behaviour { is_paused: bool, @@ -35,21 +41,19 @@ pub struct Behaviour { bootstrap_peer_address: Multiaddr, bootstrap_peer_id: PeerId, is_dialing_to_bootstrap_peer: bool, + // This needs to be boxed to allow polling it from a &mut. + sleep_future_for_dialing_bootstrap_peer: Option>, is_connected_to_bootstrap_peer: bool, is_bootstrap_in_kad_routing_table: bool, wakers: Vec, } #[derive(Debug)] -pub enum FromOtherBehaviourEvent { - KadQueryFinished, - PauseDiscovery, - ResumeDiscovery, +pub enum ToOtherBehaviourEvent { + RequestKadQuery(PeerId), + FoundListenAddresses { peer_id: PeerId, listen_addresses: Vec }, } -#[derive(Debug)] -pub struct ToOtherBehaviourEvent(KadFromOtherBehaviourEvent); - impl NetworkBehaviour for Behaviour { type ConnectionHandler = dummy::ConnectionHandler; type ToSwarm = ToOtherBehaviourEvent; @@ -80,6 +84,12 @@ impl NetworkBehaviour for Behaviour { if peer_id == self.bootstrap_peer_id => { self.is_dialing_to_bootstrap_peer = false; + // For the case that the reason for failure is consistent (e.g the bootstrap peer + // is down), we sleep before redialing + // TODO(shahak): Consider increasing the time after each failure, the same way we + // do in starknet client. + self.sleep_future_for_dialing_bootstrap_peer = + Some(tokio::time::sleep(DIAL_SLEEP).boxed()); } FromSwarm::ConnectionEstablished(ConnectionEstablished { peer_id, .. }) if peer_id == self.bootstrap_peer_id => @@ -118,7 +128,12 @@ impl NetworkBehaviour for Behaviour { ) -> Poll::FromBehaviour>> { if !self.is_dialing_to_bootstrap_peer && !self.is_connected_to_bootstrap_peer { + if let Some(sleep_future) = &mut self.sleep_future_for_dialing_bootstrap_peer { + pin_mut!(sleep_future); + ready!(sleep_future.poll(cx)); + } self.is_dialing_to_bootstrap_peer = true; + self.sleep_future_for_dialing_bootstrap_peer = None; return Poll::Ready(ToSwarm::Dial { opts: DialOpts::peer_id(self.bootstrap_peer_id) .addresses(vec![self.bootstrap_peer_address.clone()]) @@ -136,18 +151,18 @@ impl NetworkBehaviour for Behaviour { } if !self.is_bootstrap_in_kad_routing_table { self.is_bootstrap_in_kad_routing_table = true; - return Poll::Ready(ToSwarm::GenerateEvent(ToOtherBehaviourEvent( - KadFromOtherBehaviourEvent::FoundListenAddresses { + return Poll::Ready(ToSwarm::GenerateEvent( + ToOtherBehaviourEvent::FoundListenAddresses { peer_id: self.bootstrap_peer_id, listen_addresses: vec![self.bootstrap_peer_address.clone()], }, - ))); + )); } if !self.is_paused && !self.is_query_running { self.is_query_running = true; - Poll::Ready(ToSwarm::GenerateEvent(ToOtherBehaviourEvent( - KadFromOtherBehaviourEvent::RequestKadQuery(libp2p::identity::PeerId::random()), + Poll::Ready(ToSwarm::GenerateEvent(ToOtherBehaviourEvent::RequestKadQuery( + libp2p::identity::PeerId::random(), ))) } else { self.wakers.push(cx.waker().clone()); @@ -166,6 +181,7 @@ impl Behaviour { bootstrap_peer_id, bootstrap_peer_address, is_dialing_to_bootstrap_peer: false, + sleep_future_for_dialing_bootstrap_peer: None, is_connected_to_bootstrap_peer: false, is_bootstrap_in_kad_routing_table: false, wakers: Vec::new(), @@ -185,29 +201,35 @@ impl Behaviour { impl From for mixed_behaviour::Event { fn from(event: ToOtherBehaviourEvent) -> Self { - mixed_behaviour::Event::InternalEvent(mixed_behaviour::InternalEvent::NotifyKad(event.0)) + mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::Discovery(event), + ) } } impl BridgedBehaviour for Behaviour { - fn on_other_behaviour_event(&mut self, event: mixed_behaviour::InternalEvent) { - let mixed_behaviour::InternalEvent::NotifyDiscovery(event) = event else { - return; - }; + fn on_other_behaviour_event(&mut self, event: &mixed_behaviour::ToOtherBehaviourEvent) { match event { - FromOtherBehaviourEvent::PauseDiscovery => self.is_paused = true, - FromOtherBehaviourEvent::ResumeDiscovery => { + mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::PauseDiscovery, + ) => self.is_paused = true, + mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::ResumeDiscovery, + ) => { for waker in self.wakers.drain(..) { waker.wake(); } self.is_paused = false; } - FromOtherBehaviourEvent::KadQueryFinished => { + mixed_behaviour::ToOtherBehaviourEvent::Kad( + KadToOtherBehaviourEvent::KadQueryFinished, + ) => { for waker in self.wakers.drain(..) { waker.wake(); } self.is_query_running = false; } + _ => {} } } } diff --git a/crates/papyrus_network/src/gossipsub_impl.rs b/crates/papyrus_network/src/gossipsub_impl.rs new file mode 100644 index 00000000000..f2d4327ff36 --- /dev/null +++ b/crates/papyrus_network/src/gossipsub_impl.rs @@ -0,0 +1,53 @@ +use libp2p::gossipsub::TopicHash; +use libp2p::{gossipsub, PeerId}; +use tracing::error; + +use crate::mixed_behaviour; +use crate::mixed_behaviour::BridgedBehaviour; +use crate::sqmr::Bytes; + +#[cfg(test)] +pub type Topic = gossipsub::IdentTopic; +#[cfg(not(test))] +pub type Topic = gossipsub::Sha256Topic; + +#[derive(Debug)] +pub enum ExternalEvent { + #[allow(dead_code)] + Received { originated_peer_id: PeerId, message: Bytes, topic_hash: TopicHash }, +} + +impl From for mixed_behaviour::Event { + fn from(event: gossipsub::Event) -> Self { + match event { + gossipsub::Event::Message { + message: gossipsub::Message { data, topic, source, .. }, + .. + } => { + let Some(originated_peer_id) = source else { + error!( + "Received a message from gossipsub without source even though we've \ + configured it to reject such messages" + ); + return mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::NoOp, + ); + }; + mixed_behaviour::Event::ExternalEvent(mixed_behaviour::ExternalEvent::GossipSub( + ExternalEvent::Received { + originated_peer_id, + message: data, + topic_hash: topic, + }, + )) + } + _ => mixed_behaviour::Event::ToOtherBehaviourEvent( + mixed_behaviour::ToOtherBehaviourEvent::NoOp, + ), + } + } +} + +impl BridgedBehaviour for gossipsub::Behaviour { + fn on_other_behaviour_event(&mut self, _event: &mixed_behaviour::ToOtherBehaviourEvent) {} +} diff --git a/crates/papyrus_network/src/lib.rs b/crates/papyrus_network/src/lib.rs index ecf6b055106..eb328631d03 100644 --- a/crates/papyrus_network/src/lib.rs +++ b/crates/papyrus_network/src/lib.rs @@ -6,19 +6,20 @@ pub mod bin_utils; mod converters; mod db_executor; mod discovery; +mod gossipsub_impl; pub mod mixed_behaviour; pub mod network_manager; mod peer_manager; -pub mod protobuf_messages; -pub mod streamed_bytes; +pub mod sqmr; #[cfg(test)] mod test_utils; +mod utils; + use std::collections::{BTreeMap, HashMap}; use std::pin::Pin; use std::time::Duration; use std::usize; -use bytes::BufMut; use derive_more::Display; use enum_iterator::Sequence; use futures::Stream; @@ -27,10 +28,10 @@ use libp2p::{Multiaddr, StreamProtocol}; use papyrus_config::converters::deserialize_seconds_to_duration; use papyrus_config::dumping::{ser_optional_param, ser_param, SerializeConfig}; use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; -use prost::{EncodeError, Message}; -use protobuf_messages::protobuf; +use papyrus_protobuf::protobuf; +use papyrus_protobuf::sync::{Query, SignedBlockHeader}; +use prost::Message; use serde::{Deserialize, Serialize}; -use starknet_api::block::{BlockHash, BlockHeader, BlockNumber, BlockSignature}; use starknet_api::state::ThinStateDiff; // TODO: add peer manager config to the network config @@ -75,75 +76,12 @@ impl From for Protocol { } } -/// This struct represents a query that can be sent to a peer. -#[derive(Default, Debug, PartialEq, Eq)] -pub struct Query { - pub start_block: BlockNumber, - pub direction: Direction, - pub limit: usize, - pub step: usize, - pub data_type: DataType, -} - -#[derive(Debug, thiserror::Error)] -#[error("Failed to encode query")] -pub struct QueryEncodingError; - -impl Query { - pub fn encode(self, buf: &mut B) -> Result<(), QueryEncodingError> - where - B: BufMut, - { - match self.data_type { - DataType::SignedBlockHeader => { - >::into(self).encode(buf) - } - DataType::StateDiff => { - >::into(self).encode(buf) - } - } - .map_err(|_: EncodeError| QueryEncodingError) - } -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)] -#[cfg_attr(test, derive(Hash))] -pub enum Direction { - #[default] - Forward, - Backward, -} - -#[derive(Debug)] -#[cfg_attr(test, derive(Clone))] -pub struct SignedBlockHeader { - pub block_header: BlockHeader, - pub signatures: Vec, -} - -// TODO(shahak): Internalize this when we have a mixed behaviour. -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(test, derive(Hash))] -pub struct InternalQuery { - pub start_block: BlockHashOrNumber, - pub direction: Direction, - pub limit: u64, - pub step: u64, -} - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -#[cfg_attr(test, derive(Hash))] -pub enum BlockHashOrNumber { - Hash(BlockHash), - Number(BlockNumber), -} - pub type SignedBlockHeaderStream = Pin> + Send>>; pub type StateDiffStream = Pin> + Send>>; /// This struct represents the receiver end of the response streams for a network subscriber. /// It is created by the network manager and passed to the subscriber when calling -/// register_subscriber. +/// [`register_sqmr_subscriber`](`network_manager::GenericNetworkManager::register_sqmr_subscriber`). pub struct ResponseReceivers { pub signed_headers_receiver: Option, pub state_diffs_receiver: Option, @@ -165,7 +103,7 @@ impl Protocol { } } - pub fn bytes_query_to_protobuf_request(&self, query: Vec) -> InternalQuery { + pub fn bytes_query_to_protobuf_request(&self, query: Vec) -> Query { // TODO: make this function return errors instead of panicking. match self { Protocol::SignedBlockHeader => protobuf::BlockHeadersRequest::decode(&query[..]) @@ -264,14 +202,3 @@ impl Default for NetworkConfig { } } } - -impl From for InternalQuery { - fn from(query: Query) -> InternalQuery { - InternalQuery { - start_block: BlockHashOrNumber::Number(query.start_block), - direction: query.direction, - limit: query.limit as u64, - step: query.step as u64, - } - } -} diff --git a/crates/papyrus_network/src/mixed_behaviour.rs b/crates/papyrus_network/src/mixed_behaviour.rs index 4691a1bd459..dc365891b2d 100644 --- a/crates/papyrus_network/src/mixed_behaviour.rs +++ b/crates/papyrus_network/src/mixed_behaviour.rs @@ -1,16 +1,16 @@ // TODO(shahak): Erase main_behaviour and make this a separate module. -use libp2p::identity::PublicKey; +use libp2p::identity::Keypair; use libp2p::kad::store::MemoryStore; use libp2p::swarm::behaviour::toggle::Toggle; use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::NetworkBehaviour; -use libp2p::{identify, kad, Multiaddr, PeerId}; +use libp2p::{gossipsub, identify, kad, Multiaddr, PeerId}; -use crate::discovery::identify_impl::IDENTIFY_PROTOCOL_VERSION; -use crate::discovery::kad_impl::KadFromOtherBehaviourEvent; +use crate::discovery::identify_impl::{IdentifyToOtherBehaviourEvent, IDENTIFY_PROTOCOL_VERSION}; +use crate::discovery::kad_impl::KadToOtherBehaviourEvent; use crate::peer_manager::PeerManagerConfig; -use crate::{discovery, peer_manager, streamed_bytes}; +use crate::{discovery, gossipsub_impl, peer_manager, sqmr}; // TODO: consider reducing the pulicity of all behaviour to pub(crate) #[derive(NetworkBehaviour)] @@ -21,42 +21,46 @@ pub struct MixedBehaviour { pub identify: identify::Behaviour, // TODO(shahak): Consider using a different store. pub kademlia: kad::Behaviour, - pub streamed_bytes: streamed_bytes::Behaviour, + pub sqmr: sqmr::Behaviour, + pub gossipsub: gossipsub::Behaviour, } #[derive(Debug)] pub enum Event { ExternalEvent(ExternalEvent), - InternalEvent(InternalEvent), + ToOtherBehaviourEvent(ToOtherBehaviourEvent), } #[derive(Debug)] pub enum ExternalEvent { - StreamedBytes(streamed_bytes::behaviour::ExternalEvent), + Sqmr(sqmr::behaviour::ExternalEvent), + GossipSub(gossipsub_impl::ExternalEvent), } #[derive(Debug)] -pub enum InternalEvent { +pub enum ToOtherBehaviourEvent { NoOp, - NotifyKad(KadFromOtherBehaviourEvent), - NotifyDiscovery(discovery::FromOtherBehaviourEvent), - NotifyPeerManager(peer_manager::FromOtherBehaviour), - NotifyStreamedBytes(streamed_bytes::behaviour::FromOtherBehaviour), + Identify(IdentifyToOtherBehaviourEvent), + Kad(KadToOtherBehaviourEvent), + Discovery(discovery::ToOtherBehaviourEvent), + PeerManager(peer_manager::ToOtherBehaviourEvent), + Sqmr(sqmr::ToOtherBehaviourEvent), } pub trait BridgedBehaviour { - fn on_other_behaviour_event(&mut self, event: InternalEvent); + fn on_other_behaviour_event(&mut self, event: &ToOtherBehaviourEvent); } impl MixedBehaviour { // TODO: get config details from network manager config /// Panics if bootstrap_peer_multiaddr doesn't have a peer id. pub fn new( - key: PublicKey, + keypair: Keypair, bootstrap_peer_multiaddr: Option, - streamed_bytes_config: streamed_bytes::Config, + streamed_bytes_config: sqmr::Config, ) -> Self { - let local_peer_id = PeerId::from_public_key(&key); + let public_key = keypair.public(); + let local_peer_id = PeerId::from_public_key(&public_key); Self { peer_manager: peer_manager::PeerManager::new(PeerManagerConfig::default()), discovery: bootstrap_peer_multiaddr @@ -71,11 +75,20 @@ impl MixedBehaviour { .into(), identify: identify::Behaviour::new(identify::Config::new( IDENTIFY_PROTOCOL_VERSION.to_string(), - key, + public_key, )), // TODO: change kademlia protocol name kademlia: kad::Behaviour::new(local_peer_id, MemoryStore::new(local_peer_id)), - streamed_bytes: streamed_bytes::Behaviour::new(streamed_bytes_config), + sqmr: sqmr::Behaviour::new(streamed_bytes_config), + gossipsub: gossipsub::Behaviour::new( + gossipsub::MessageAuthenticity::Signed(keypair), + gossipsub::Config::default(), + ) + .unwrap_or_else(|err_string| { + panic!( + "Failed creating gossipsub behaviour due to the following error: {err_string}" + ) + }), } } } diff --git a/crates/papyrus_network/src/network_manager/mod.rs b/crates/papyrus_network/src/network_manager/mod.rs index 8469b040ceb..7af2c3b9eee 100644 --- a/crates/papyrus_network/src/network_manager/mod.rs +++ b/crates/papyrus_network/src/network_manager/mod.rs @@ -5,27 +5,34 @@ mod test; use std::collections::HashMap; -use futures::channel::mpsc::{Receiver, Sender}; -use futures::future::pending; -use futures::stream::{self, BoxStream, SelectAll}; -use futures::{FutureExt, StreamExt}; +use futures::channel::mpsc::{Receiver, SendError, Sender, UnboundedReceiver, UnboundedSender}; +use futures::future::{pending, ready, Ready}; +use futures::sink::With; +use futures::stream::{self, BoxStream, Map, SelectAll}; +use futures::{FutureExt, SinkExt, StreamExt}; +use libp2p::gossipsub::{SubscriptionError, TopicHash}; use libp2p::swarm::SwarmEvent; use libp2p::{PeerId, Swarm}; use metrics::gauge; use papyrus_common::metrics as papyrus_metrics; +use papyrus_protobuf::protobuf; use papyrus_storage::StorageReader; +use prost::Message; +use sqmr::Bytes; use tracing::{debug, error, info, trace}; use self::swarm_trait::SwarmTrait; use crate::bin_utils::build_swarm; use crate::converters::{Router, RouterError}; -use crate::db_executor::{self, BlockHeaderDBExecutor, DBExecutor, Data, QueryId}; +use crate::db_executor::{self, DBExecutor, DBExecutorTrait, Data, QueryId}; +use crate::gossipsub_impl::Topic; use crate::mixed_behaviour::{self, BridgedBehaviour}; -use crate::streamed_bytes::{self, InboundSessionId, OutboundSessionId, SessionId}; -use crate::{DataType, NetworkConfig, Protocol, Query, ResponseReceivers}; +use crate::sqmr::{self, InboundSessionId, OutboundSessionId, SessionId}; +use crate::utils::StreamHashMap; +use crate::{gossipsub_impl, DataType, NetworkConfig, Protocol, Query, ResponseReceivers}; type StreamCollection = SelectAll>; -type SubscriberChannels = (Receiver, Router); +type SubscriberChannels = (Receiver<(Query, DataType)>, Router); #[derive(thiserror::Error, Debug)] pub enum NetworkError { @@ -33,20 +40,28 @@ pub enum NetworkError { DialError(#[from] libp2p::swarm::DialError), } -pub struct GenericNetworkManager { +pub struct GenericNetworkManager { swarm: SwarmT, db_executor: DBExecutorT, header_buffer_size: usize, query_results_router: StreamCollection, sync_subscriber_channels: Option, + // Splitting the broadcast receivers from the broadcasted senders in order to poll all + // receivers simultaneously. + // Each receiver has a matching sender and vice versa (i.e the maps have the same keys). + messages_to_broadcast_receivers: StreamHashMap>, + broadcasted_messages_senders: HashMap>, query_id_to_inbound_session_id: HashMap, outbound_session_id_to_protocol: HashMap, + reported_peer_receiver: UnboundedReceiver, + // We keep this just for giving a clone of it for subscribers. + reported_peer_sender: UnboundedSender, // Fields for metrics num_active_inbound_sessions: usize, num_active_outbound_sessions: usize, } -impl GenericNetworkManager { +impl GenericNetworkManager { pub async fn run(mut self) -> Result<(), NetworkError> { loop { tokio::select! { @@ -54,35 +69,44 @@ impl GenericNetworkManager self.handle_db_executor_result(res), Some(res) = self.query_results_router.next() => self.handle_query_result_routing_to_other_peer(res), Some(res) = self.sync_subscriber_channels.as_mut() - .map(|(query_receiver, _)| query_receiver.next().boxed()) - .unwrap_or(pending().boxed()) => self.handle_sync_subscriber_query(res), + .map(|(query_receiver, _)| query_receiver.next().boxed()) + .unwrap_or(pending().boxed()) => self.handle_sync_subscriber_query(res.0, res.1), + Some((topic_hash, message)) = self.messages_to_broadcast_receivers.next() => { + self.broadcast_message(message, topic_hash); + } + Some(peer_id) = self.reported_peer_receiver.next() => self.swarm.report_peer(peer_id), } } } - pub(self) fn generic_new( + pub(crate) fn generic_new( swarm: SwarmT, db_executor: DBExecutorT, header_buffer_size: usize, ) -> Self { gauge!(papyrus_metrics::PAPYRUS_NUM_CONNECTED_PEERS, 0f64); + let (reported_peer_sender, reported_peer_receiver) = futures::channel::mpsc::unbounded(); Self { swarm, db_executor, header_buffer_size, query_results_router: StreamCollection::new(), sync_subscriber_channels: None, + messages_to_broadcast_receivers: StreamHashMap::new(HashMap::new()), + broadcasted_messages_senders: HashMap::new(), query_id_to_inbound_session_id: HashMap::new(), outbound_session_id_to_protocol: HashMap::new(), + reported_peer_sender, + reported_peer_receiver, num_active_inbound_sessions: 0, num_active_outbound_sessions: 0, } } - pub fn register_subscriber( + pub fn register_sqmr_subscriber( &mut self, protocols: Vec, - ) -> (Sender, ResponseReceivers) { + ) -> (Sender<(Query, DataType)>, ResponseReceivers) { let (sender, query_receiver) = futures::channel::mpsc::channel(self.header_buffer_size); let mut router = Router::new(protocols, self.header_buffer_size); let response_receiver = ResponseReceivers::new(router.get_recievers()); @@ -90,6 +114,58 @@ impl GenericNetworkManager( + &mut self, + topic: Topic, + buffer_size: usize, + ) -> Result>::Error>, SubscriptionError> + where + T: TryFrom, + Bytes: From, + { + self.swarm.subscribe_to_topic(&topic)?; + + let topic_hash = topic.hash(); + + let (messages_to_broadcast_sender, messages_to_broadcast_receiver) = + futures::channel::mpsc::channel(buffer_size); + let (broadcasted_messages_sender, broadcasted_messages_receiver) = + futures::channel::mpsc::channel(buffer_size); + + let insert_result = self + .messages_to_broadcast_receivers + .insert(topic_hash.clone(), messages_to_broadcast_receiver); + if insert_result.is_some() { + panic!("Topic '{}' has already been registered.", topic); + } + + let insert_result = self + .broadcasted_messages_senders + .insert(topic_hash.clone(), broadcasted_messages_sender); + if insert_result.is_some() { + panic!("Topic '{}' has already been registered.", topic); + } + + let messages_to_broadcast_fn: fn(T) -> Ready> = + |x| ready(Ok(Bytes::from(x))); + let messages_to_broadcast_sender = + messages_to_broadcast_sender.with(messages_to_broadcast_fn); + + let broadcasted_messages_fn: BroadcastedMessagesConverterFn< + T, + >::Error, + > = |(x, report_callback)| (T::try_from(x), report_callback); + let broadcasted_messages_receiver = + broadcasted_messages_receiver.map(broadcasted_messages_fn); + + Ok(BroadcastSubscriberChannels { + messages_to_broadcast_sender, + broadcasted_messages_receiver, + }) + } + fn handle_swarm_event(&mut self, event: SwarmEvent) { match event { SwarmEvent::ConnectionEstablished { peer_id, .. } => { @@ -171,46 +247,41 @@ impl GenericNetworkManager { self.handle_behaviour_external_event(external_event); } - mixed_behaviour::Event::InternalEvent(internal_event) => { - self.handle_behaviour_internal_event(internal_event); + mixed_behaviour::Event::ToOtherBehaviourEvent(internal_event) => { + self.handle_to_other_behaviour_event(internal_event); } } } fn handle_behaviour_external_event(&mut self, event: mixed_behaviour::ExternalEvent) { match event { - mixed_behaviour::ExternalEvent::StreamedBytes(event) => { + mixed_behaviour::ExternalEvent::Sqmr(event) => { self.handle_stream_bytes_behaviour_event(event); } + mixed_behaviour::ExternalEvent::GossipSub(event) => { + self.handle_gossipsub_behaviour_event(event); + } } } - fn handle_behaviour_internal_event(&mut self, event: mixed_behaviour::InternalEvent) { - match event { - mixed_behaviour::InternalEvent::NoOp => {} - mixed_behaviour::InternalEvent::NotifyKad(_) => { - self.swarm.behaviour_mut().kademlia.on_other_behaviour_event(event) - } - mixed_behaviour::InternalEvent::NotifyDiscovery(_) => { - if let Some(discovery) = self.swarm.behaviour_mut().discovery.as_mut() { - discovery.on_other_behaviour_event(event); - } - } - mixed_behaviour::InternalEvent::NotifyStreamedBytes(_) => { - self.swarm.behaviour_mut().streamed_bytes.on_other_behaviour_event(event) - } - mixed_behaviour::InternalEvent::NotifyPeerManager(_) => { - self.swarm.behaviour_mut().peer_manager.on_other_behaviour_event(event) - } + fn handle_to_other_behaviour_event(&mut self, event: mixed_behaviour::ToOtherBehaviourEvent) { + // TODO(shahak): Move this logic to mixed_behaviour. + if let mixed_behaviour::ToOtherBehaviourEvent::NoOp = event { + return; + } + self.swarm.behaviour_mut().identify.on_other_behaviour_event(&event); + self.swarm.behaviour_mut().kademlia.on_other_behaviour_event(&event); + if let Some(discovery) = self.swarm.behaviour_mut().discovery.as_mut() { + discovery.on_other_behaviour_event(&event); } + self.swarm.behaviour_mut().sqmr.on_other_behaviour_event(&event); + self.swarm.behaviour_mut().peer_manager.on_other_behaviour_event(&event); + self.swarm.behaviour_mut().gossipsub.on_other_behaviour_event(&event); } - fn handle_stream_bytes_behaviour_event( - &mut self, - event: streamed_bytes::behaviour::ExternalEvent, - ) { + fn handle_stream_bytes_behaviour_event(&mut self, event: sqmr::behaviour::ExternalEvent) { match event { - streamed_bytes::behaviour::ExternalEvent::NewInboundSession { + sqmr::behaviour::ExternalEvent::NewInboundSession { query, inbound_session_id, peer_id: _, @@ -235,15 +306,13 @@ impl GenericNetworkManager { + sqmr::behaviour::ExternalEvent::ReceivedData { outbound_session_id, data } => { trace!( "Received data from peer for session id: {outbound_session_id:?}. sending to \ sync subscriber." @@ -274,7 +343,7 @@ impl GenericNetworkManager { + sqmr::behaviour::ExternalEvent::SessionFailed { session_id, error } => { error!("Session {session_id:?} failed on {error:?}"); self.report_session_removed_to_metrics(session_id); // TODO: Handle reputation and retry. @@ -282,9 +351,7 @@ impl GenericNetworkManager { + sqmr::behaviour::ExternalEvent::SessionFinishedSuccessfully { session_id } => { debug!("Session completed successfully. session_id: {session_id:?}"); self.report_session_removed_to_metrics(session_id); if let SessionId::OutboundSessionId(outbound_session_id) = session_id { @@ -294,6 +361,38 @@ impl GenericNetworkManager { + let Some(sender) = self.broadcasted_messages_senders.get_mut(&topic_hash) else { + error!( + "Received a message from a topic we're not subscribed to with hash \ + {topic_hash:?}" + ); + return; + }; + let reported_peer_sender = self.reported_peer_sender.clone(); + let send_result = sender.try_send(( + message, + Box::new(move || { + // TODO(shahak): Check if we can panic in case of error. + let _ = reported_peer_sender.unbounded_send(originated_peer_id); + }), + )); + if let Err(e) = send_result { + if e.is_disconnected() { + panic!("Receiver was dropped. This should never happen.") + } else if e.is_full() { + error!( + "Receiver buffer is full. Dropping broadcasted message for topic with \ + hash: {topic_hash:?}." + ); + } + } + } + } + } + fn handle_query_result_routing_to_other_peer(&mut self, res: (Data, InboundSessionId)) { if self.query_results_router.is_empty() { // We're done handling all the queries we had and the stream is exhausted. @@ -320,11 +419,14 @@ impl GenericNetworkManager { + protobuf::BlockHeadersRequest::from(query).encode_to_vec() + } + DataType::StateDiff => protobuf::StateDiffsRequest::from(query).encode_to_vec(), + }; match self.swarm.send_query(query_bytes, PeerId::random(), protocol) { Ok(outbound_session_id) => { debug!("Sent query to peer. outbound_session_id: {outbound_session_id:?}"); @@ -344,6 +446,10 @@ impl GenericNetworkManager { @@ -364,8 +470,7 @@ impl GenericNetworkManager>; +pub type NetworkManager = GenericNetworkManager>; impl NetworkManager { pub fn new(config: NetworkConfig, storage_reader: StorageReader) -> Self { @@ -387,7 +492,7 @@ impl NetworkManager { mixed_behaviour::MixedBehaviour::new( key, bootstrap_peer_multiaddr.clone(), - streamed_bytes::Config { + sqmr::Config { session_timeout, supported_inbound_protocols: vec![ Protocol::SignedBlockHeader.into(), @@ -397,7 +502,7 @@ impl NetworkManager { ) }); - let db_executor = BlockHeaderDBExecutor::new(storage_reader); + let db_executor = DBExecutor::new(storage_reader); Self::generic_new(swarm, db_executor, header_buffer_size) } @@ -405,3 +510,25 @@ impl NetworkManager { self.swarm.local_peer_id().to_string() } } + +// TODO(shahak): Change to a wrapper of PeerId if Box dyn becomes an overhead. +pub type ReportCallback = Box; + +pub type MessagesToBroadcastSender = With< + Sender, + Bytes, + T, + Ready>, + fn(T) -> Ready>, +>; + +pub type BroadcastedMessagesReceiver = + Map, BroadcastedMessagesConverterFn>; + +type BroadcastedMessagesConverterFn = + fn((Bytes, ReportCallback)) -> (Result, ReportCallback); + +pub struct BroadcastSubscriberChannels { + pub messages_to_broadcast_sender: MessagesToBroadcastSender, + pub broadcasted_messages_receiver: BroadcastedMessagesReceiver, +} diff --git a/crates/papyrus_network/src/network_manager/swarm_trait.rs b/crates/papyrus_network/src/network_manager/swarm_trait.rs index 9ad252ed9df..f168c5b05cc 100644 --- a/crates/papyrus_network/src/network_manager/swarm_trait.rs +++ b/crates/papyrus_network/src/network_manager/swarm_trait.rs @@ -1,10 +1,14 @@ use futures::stream::Stream; +use libp2p::gossipsub::{SubscriptionError, TopicHash}; use libp2p::swarm::dial_opts::DialOpts; use libp2p::swarm::{DialError, NetworkBehaviour, SwarmEvent}; use libp2p::{Multiaddr, PeerId, Swarm}; +use tracing::error; -use crate::streamed_bytes::behaviour::{PeerNotConnected, SessionIdNotFoundError}; -use crate::streamed_bytes::{InboundSessionId, OutboundSessionId}; +use crate::gossipsub_impl::Topic; +use crate::peer_manager::ReputationModifier; +use crate::sqmr::behaviour::{PeerNotConnected, SessionIdNotFoundError}; +use crate::sqmr::{Bytes, InboundSessionId, OutboundSessionId}; use crate::{mixed_behaviour, Protocol}; pub type Event = SwarmEvent<::ToSwarm>; @@ -35,6 +39,12 @@ pub trait SwarmTrait: Stream + Unpin { fn behaviour_mut(&mut self) -> &mut mixed_behaviour::MixedBehaviour; fn add_external_address(&mut self, address: Multiaddr); + + fn subscribe_to_topic(&mut self, topic: &Topic) -> Result<(), SubscriptionError>; + + fn broadcast_message(&mut self, message: Bytes, topic_hash: TopicHash); + + fn report_peer(&mut self, peer_id: PeerId); } impl SwarmTrait for Swarm { @@ -43,7 +53,7 @@ impl SwarmTrait for Swarm { data: Vec, inbound_session_id: InboundSessionId, ) -> Result<(), SessionIdNotFoundError> { - self.behaviour_mut().streamed_bytes.send_length_prefixed_data(data, inbound_session_id) + self.behaviour_mut().sqmr.send_length_prefixed_data(data, inbound_session_id) } // TODO: change this function signature @@ -53,7 +63,7 @@ impl SwarmTrait for Swarm { _peer_id: PeerId, protocol: Protocol, ) -> Result { - Ok(self.behaviour_mut().streamed_bytes.start_query(query, protocol.into())) + Ok(self.behaviour_mut().sqmr.start_query(query, protocol.into())) } fn dial(&mut self, peer_multiaddr: Multiaddr) -> Result<(), DialError> { @@ -67,7 +77,7 @@ impl SwarmTrait for Swarm { &mut self, session_id: InboundSessionId, ) -> Result<(), SessionIdNotFoundError> { - self.behaviour_mut().streamed_bytes.close_inbound_session(session_id) + self.behaviour_mut().sqmr.close_inbound_session(session_id) } fn behaviour_mut(&mut self) -> &mut mixed_behaviour::MixedBehaviour { @@ -77,4 +87,24 @@ impl SwarmTrait for Swarm { fn add_external_address(&mut self, address: Multiaddr) { self.add_external_address(address); } + + fn subscribe_to_topic(&mut self, topic: &Topic) -> Result<(), SubscriptionError> { + self.behaviour_mut().gossipsub.subscribe(topic).map(|_| ()) + } + + fn broadcast_message(&mut self, message: Bytes, topic_hash: TopicHash) { + let result = self.behaviour_mut().gossipsub.publish(topic_hash.clone(), message); + if let Err(err) = result { + // TODO(shahak): Consider reporting to the subscriber broadcast failures or retrying + // upon failure. + error!( + "Error occured while broadcasting a message to the topic with hash \ + {topic_hash:?}: {err:?}" + ); + } + } + + fn report_peer(&mut self, peer_id: PeerId) { + let _ = self.behaviour_mut().peer_manager.report_peer(peer_id, ReputationModifier::Bad {}); + } } diff --git a/crates/papyrus_network/src/network_manager/test.rs b/crates/papyrus_network/src/network_manager/test.rs index a047f4c555a..d86791f9bf5 100644 --- a/crates/papyrus_network/src/network_manager/test.rs +++ b/crates/papyrus_network/src/network_manager/test.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; @@ -12,8 +12,11 @@ use futures::future::{poll_fn, FutureExt}; use futures::stream::{FuturesUnordered, Stream}; use futures::{pin_mut, Future, SinkExt, StreamExt}; use libp2p::core::ConnectedPoint; +use libp2p::gossipsub::{SubscriptionError, TopicHash}; use libp2p::swarm::ConnectionId; use libp2p::{Multiaddr, PeerId}; +use papyrus_protobuf::protobuf; +use papyrus_protobuf::sync::{BlockHashOrNumber, Direction, Query, SignedBlockHeader}; use prost::Message; use starknet_api::block::{BlockHeader, BlockNumber}; use tokio::select; @@ -25,21 +28,26 @@ use super::swarm_trait::{Event, SwarmTrait}; use super::GenericNetworkManager; use crate::db_executor::{ poll_query_execution_set, - DBExecutor, DBExecutorError, + DBExecutorTrait, Data, FetchBlockDataFromDb, QueryId, }; -use crate::protobuf_messages::protobuf; -use crate::streamed_bytes::behaviour::{PeerNotConnected, SessionIdNotFoundError}; -use crate::streamed_bytes::{GenericEvent, InboundSessionId, OutboundSessionId}; -use crate::{mixed_behaviour, BlockHashOrNumber, DataType, Direction, InternalQuery, Query}; +use crate::gossipsub_impl::{self, Topic}; +use crate::sqmr::behaviour::{PeerNotConnected, SessionIdNotFoundError}; +use crate::sqmr::{Bytes, GenericEvent, InboundSessionId, OutboundSessionId}; +use crate::{mixed_behaviour, DataType}; + +const TIMEOUT: Duration = Duration::from_secs(1); #[derive(Default)] struct MockSwarm { pub pending_events: Queue, - pub sent_queries: Vec<(InternalQuery, PeerId)>, + pub sent_queries: Vec<(Query, PeerId)>, + pub subscribed_topics: HashSet, + broadcasted_messages_senders: Vec>, + reported_peer_senders: Vec>, inbound_session_id_to_data_sender: HashMap>, next_outbound_session_id: usize, first_polled_event_notifier: Option>, @@ -78,9 +86,21 @@ impl MockSwarm { data_receiver.collect() } + pub fn stream_messages_we_broadcasted(&mut self) -> impl Stream { + let (sender, receiver) = unbounded(); + self.broadcasted_messages_senders.push(sender); + receiver + } + + pub fn get_reported_peers_stream(&mut self) -> impl Stream { + let (sender, receiver) = unbounded(); + self.reported_peer_senders.push(sender); + receiver + } + fn create_received_data_events_for_query( &self, - query: InternalQuery, + query: Query, outbound_session_id: OutboundSessionId, ) { let BlockHashOrNumber::Number(BlockNumber(start_block_number)) = query.start_block else { @@ -90,23 +110,17 @@ impl MockSwarm { for block_number in (start_block_number..block_max_number) .step_by(query.step.try_into().expect("step too large to convert to usize")) { - let signed_header = Data::BlockHeaderAndSignature { - header: BlockHeader { + let signed_header = SignedBlockHeader { + block_header: BlockHeader { block_number: BlockNumber(block_number), ..Default::default() }, signatures: vec![], }; - let mut data_bytes = vec![]; - protobuf::BlockHeadersResponse::try_from(signed_header) - .expect( - "Data::BlockHeaderAndSignature should be convertable to \ - protobuf::BlockHeadersResponse", - ) - .encode(&mut data_bytes) - .expect("failed to convert data to bytes"); + let data_bytes = + protobuf::BlockHeadersResponse::from(Some(signed_header)).encode_to_vec(); self.pending_events.push(Event::Behaviour(mixed_behaviour::Event::ExternalEvent( - mixed_behaviour::ExternalEvent::StreamedBytes(GenericEvent::ReceivedData { + mixed_behaviour::ExternalEvent::Sqmr(GenericEvent::ReceivedData { data: data_bytes, outbound_session_id, }), @@ -125,12 +139,17 @@ impl SwarmTrait for MockSwarm { "Called send_length_prefixed_data without calling get_data_sent_to_inbound_session \ first", ); - // TODO(shahak): Add tests for state diff. - let data = protobuf::BlockHeadersResponse::decode_length_delimited(&data[..]) + let decoded_data = protobuf::BlockHeadersResponse::decode_length_delimited(&data[..]) .unwrap() .try_into() .unwrap(); - let is_fin = matches!(data, Data::Fin(DataType::SignedBlockHeader)); + // TODO(shahak): Add tests for state diff. + let (data, is_fin) = match decoded_data { + Some(signed_block_header) => { + (Data::BlockHeaderAndSignature(signed_block_header), false) + } + None => (Data::Fin(DataType::SignedBlockHeader), true), + }; data_sender.unbounded_send(data).unwrap(); if is_fin { data_sender.close_channel(); @@ -144,11 +163,11 @@ impl SwarmTrait for MockSwarm { peer_id: PeerId, _protocol: crate::Protocol, ) -> Result { - let query = protobuf::BlockHeadersRequest::decode(&query[..]) + let query: Query = protobuf::BlockHeadersRequest::decode(&query[..]) .expect("failed to decode protobuf BlockHeadersRequest") .try_into() .expect("failed to convert BlockHeadersRequest"); - self.sent_queries.push((query, peer_id)); + self.sent_queries.push((query.clone(), peer_id)); let outbound_session_id = OutboundSessionId { value: self.next_outbound_session_id }; self.create_received_data_events_for_query(query, outbound_session_id); self.next_outbound_session_id += 1; @@ -176,12 +195,29 @@ impl SwarmTrait for MockSwarm { } fn add_external_address(&mut self, _address: Multiaddr) {} + + fn subscribe_to_topic(&mut self, topic: &Topic) -> Result<(), SubscriptionError> { + self.subscribed_topics.insert(topic.hash()); + Ok(()) + } + + fn broadcast_message(&mut self, message: Bytes, topic_hash: TopicHash) { + for sender in &self.broadcasted_messages_senders { + sender.unbounded_send((message.clone(), topic_hash.clone())).unwrap(); + } + } + + fn report_peer(&mut self, peer_id: PeerId) { + for sender in &self.reported_peer_senders { + sender.unbounded_send(peer_id).unwrap(); + } + } } #[derive(Default)] struct MockDBExecutor { next_query_id: usize, - pub query_to_headers: HashMap>, + pub query_to_headers: HashMap>, query_execution_set: FuturesUnordered>>, } @@ -193,13 +229,13 @@ impl Stream for MockDBExecutor { } } -impl DBExecutor for MockDBExecutor { - // TODO(shahak): Consider fixing code duplication with BlockHeaderDBExecutor. +impl DBExecutorTrait for MockDBExecutor { + // TODO(shahak): Consider fixing code duplication with DBExecutor. fn register_query( &mut self, - query: InternalQuery, + query: Query, _data_type: impl FetchBlockDataFromDb + Send, - mut sender: Sender, + mut sender: Sender>, ) -> QueryId { let query_id = QueryId(self.next_query_id); self.next_query_id += 1; @@ -209,10 +245,9 @@ impl DBExecutor for MockDBExecutor { for header in headers.iter().cloned() { // Using poll_fn because Sender::poll_ready is not a future if let Ok(()) = poll_fn(|cx| sender.poll_ready(cx)).await { - if let Err(e) = sender.start_send(Data::BlockHeaderAndSignature { - header, - signatures: vec![], - }) { + if let Err(e) = sender.start_send(vec![Data::BlockHeaderAndSignature( + SignedBlockHeader { block_header: header, signatures: vec![] }, + )]) { return Err(DBExecutorError::SendError { query_id, send_error: e }); }; } @@ -224,7 +259,7 @@ impl DBExecutor for MockDBExecutor { } } -const HEADER_BUFFER_SIZE: usize = 100; +const BUFFER_SIZE: usize = 100; #[tokio::test] async fn register_subscriber_and_use_channels() { @@ -236,25 +271,21 @@ async fn register_subscriber_and_use_channels() { mock_swarm.first_polled_event_notifier = Some(event_notifier); // network manager to register subscriber and send query - let mut network_manager = GenericNetworkManager::generic_new( - mock_swarm, - MockDBExecutor::default(), - HEADER_BUFFER_SIZE, - ); + let mut network_manager = + GenericNetworkManager::generic_new(mock_swarm, MockDBExecutor::default(), BUFFER_SIZE); // define query - let query_limit = 5; + let query_limit: usize = 5; let start_block_number = 0; let query = Query { - start_block: BlockNumber(start_block_number), + start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), direction: Direction::Forward, - limit: query_limit, + limit: query_limit.try_into().unwrap(), step: 1, - data_type: DataType::SignedBlockHeader, }; // register subscriber and send query let (mut query_sender, response_receivers) = - network_manager.register_subscriber(vec![crate::Protocol::SignedBlockHeader]); + network_manager.register_sqmr_subscriber(vec![crate::Protocol::SignedBlockHeader]); let signed_header_receiver_length = Arc::new(Mutex::new(0)); let cloned_signed_header_receiver_length = Arc::clone(&signed_header_receiver_length); @@ -271,7 +302,8 @@ async fn register_subscriber_and_use_channels() { tokio::select! { _ = network_manager.run() => panic!("network manager ended"), _ = poll_fn(|cx| event_listner.poll_unpin(cx)).then(|_| async move { - query_sender.send(query).await.unwrap()}).then(|_| async move { + query_sender.send((query, DataType::SignedBlockHeader)).await.unwrap()}) + .then(|_| async move { *cloned_signed_header_receiver_length.lock().await = signed_header_receiver_collector.await.len(); }) => {}, _ = sleep(Duration::from_secs(5)) => { @@ -285,7 +317,7 @@ async fn register_subscriber_and_use_channels() { async fn process_incoming_query() { // Create data for test. const BLOCK_NUM: u64 = 0; - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(BLOCK_NUM)), direction: Direction::Forward, limit: 5, @@ -299,7 +331,7 @@ async fn process_incoming_query() { // Setup mock DB executor and tell it to reply to the query with the given headers. let mut mock_db_executor = MockDBExecutor::default(); - mock_db_executor.query_to_headers.insert(query, headers.clone()); + mock_db_executor.query_to_headers.insert(query.clone(), headers.clone()); // Setup mock swarm and tell it to return an event of new inbound query. let mut mock_swarm = MockSwarm::default(); @@ -316,7 +348,7 @@ async fn process_incoming_query() { .encode(&mut query_bytes) .unwrap(); mock_swarm.pending_events.push(Event::Behaviour(mixed_behaviour::Event::ExternalEvent( - mixed_behaviour::ExternalEvent::StreamedBytes(GenericEvent::NewInboundSession { + mixed_behaviour::ExternalEvent::Sqmr(GenericEvent::NewInboundSession { query: query_bytes, inbound_session_id, peer_id: PeerId::random(), @@ -328,16 +360,17 @@ async fn process_incoming_query() { let get_data_fut = mock_swarm.get_data_sent_to_inbound_session(inbound_session_id); let network_manager = - GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, HEADER_BUFFER_SIZE); + GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, BUFFER_SIZE); select! { inbound_session_data = get_data_fut => { let mut expected_data = headers .into_iter() .map(|header| { - Data::BlockHeaderAndSignature { - header, signatures: vec![] - }}) + Data::BlockHeaderAndSignature(SignedBlockHeader { + block_header: header, signatures: vec![] + }) + }) .collect::>(); expected_data.push(Data::Fin(DataType::SignedBlockHeader)); assert_eq!(inbound_session_data, expected_data); @@ -356,7 +389,7 @@ async fn close_inbound_session() { // define query let query_limit = 5; let start_block_number = 0; - let query = InternalQuery { + let query = Query { start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), direction: Direction::Forward, limit: query_limit, @@ -386,7 +419,7 @@ async fn close_inbound_session() { let inbound_session_id = InboundSessionId { value: 0 }; let _fut = mock_swarm.get_data_sent_to_inbound_session(inbound_session_id); mock_swarm.pending_events.push(Event::Behaviour(mixed_behaviour::Event::ExternalEvent( - mixed_behaviour::ExternalEvent::StreamedBytes(GenericEvent::NewInboundSession { + mixed_behaviour::ExternalEvent::Sqmr(GenericEvent::NewInboundSession { query: query_bytes, inbound_session_id, peer_id: PeerId::random(), @@ -400,7 +433,7 @@ async fn close_inbound_session() { // Create network manager and run it let network_manager = - GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, HEADER_BUFFER_SIZE); + GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, BUFFER_SIZE); tokio::select! { _ = network_manager.run() => panic!("network manager ended"), _ = inbound_session_closed_receiver => {} @@ -410,6 +443,77 @@ async fn close_inbound_session() { } } +#[tokio::test] +async fn broadcast_message() { + let topic = Topic::new("TOPIC"); + let message = vec![1u8, 2u8, 3u8]; + + let mut mock_swarm = MockSwarm::default(); + let mut messages_we_broadcasted_stream = mock_swarm.stream_messages_we_broadcasted(); + + let mock_db_executor = MockDBExecutor::default(); + let mut network_manager = + GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, BUFFER_SIZE); + + let mut messages_to_broadcast_sender = network_manager + .register_broadcast_subscriber(topic.clone(), BUFFER_SIZE) + .unwrap() + .messages_to_broadcast_sender; + messages_to_broadcast_sender.send(message.clone()).await.unwrap(); + + tokio::select! { + _ = network_manager.run() => panic!("network manager ended"), + result = tokio::time::timeout( + TIMEOUT, messages_we_broadcasted_stream.next() + ) => { + let (actual_message, topic_hash) = result.unwrap().unwrap(); + assert_eq!(message, actual_message); + assert_eq!(topic.hash(), topic_hash); + } + } +} + +#[tokio::test] +async fn receive_broadcasted_message_and_report_it() { + let topic = Topic::new("TOPIC"); + let message = vec![1u8, 2u8, 3u8]; + let originated_peer_id = PeerId::random(); + + let mut mock_swarm = MockSwarm::default(); + mock_swarm.pending_events.push(Event::Behaviour(mixed_behaviour::Event::ExternalEvent( + mixed_behaviour::ExternalEvent::GossipSub(gossipsub_impl::ExternalEvent::Received { + originated_peer_id, + message: message.clone(), + topic_hash: topic.hash(), + }), + ))); + let mut reported_peer_receiver = mock_swarm.get_reported_peers_stream(); + + let mock_db_executor = MockDBExecutor::default(); + let mut network_manager = + GenericNetworkManager::generic_new(mock_swarm, mock_db_executor, BUFFER_SIZE); + + let mut broadcasted_messages_receiver = network_manager + .register_broadcast_subscriber::(topic.clone(), BUFFER_SIZE) + .unwrap() + .broadcasted_messages_receiver; + + tokio::select! { + _ = network_manager.run() => panic!("network manager ended"), + // We need to do the entire calculation in the future here so that the network will keep + // running while we call report_callback. + reported_peer_result = tokio::time::timeout(TIMEOUT, broadcasted_messages_receiver.next()) + .then(|result| { + let (message_result, report_callback) = result.unwrap().unwrap(); + assert_eq!(message, message_result.unwrap()); + report_callback(); + tokio::time::timeout(TIMEOUT, reported_peer_receiver.next()) + }) => { + assert_eq!(originated_peer_id, reported_peer_result.unwrap().unwrap()); + } + } +} + fn get_test_connection_established_event(mock_peer_id: PeerId) -> Event { Event::ConnectionEstablished { peer_id: mock_peer_id, diff --git a/crates/papyrus_network/src/peer_manager/behaviour_impl.rs b/crates/papyrus_network/src/peer_manager/behaviour_impl.rs index 2d9a1afc211..3683f2b44a9 100644 --- a/crates/papyrus_network/src/peer_manager/behaviour_impl.rs +++ b/crates/papyrus_network/src/peer_manager/behaviour_impl.rs @@ -1,18 +1,31 @@ use std::task::Poll; use libp2p::swarm::behaviour::ConnectionEstablished; -use libp2p::swarm::{dummy, ConnectionClosed, DialError, DialFailure, NetworkBehaviour, ToSwarm}; -use libp2p::Multiaddr; +use libp2p::swarm::{ + dummy, + ConnectionClosed, + ConnectionId, + DialError, + DialFailure, + NetworkBehaviour, + ToSwarm, +}; +use libp2p::{Multiaddr, PeerId}; use tracing::{debug, error}; use super::peer::PeerTrait; use super::{PeerManager, PeerManagerError}; -use crate::{discovery, streamed_bytes}; +use crate::sqmr::OutboundSessionId; #[derive(Debug)] -pub enum Event { - NotifyStreamedBytes(streamed_bytes::behaviour::FromOtherBehaviour), - NotifyDiscovery(discovery::FromOtherBehaviourEvent), +pub enum ToOtherBehaviourEvent { + SessionAssigned { + outbound_session_id: OutboundSessionId, + peer_id: PeerId, + connection_id: ConnectionId, + }, + PauseDiscovery, + ResumeDiscovery, } impl NetworkBehaviour for PeerManager

@@ -20,7 +33,7 @@ where P: PeerTrait, { type ConnectionHandler = dummy::ConnectionHandler; - type ToSwarm = Event; + type ToSwarm = ToOtherBehaviourEvent; fn handle_established_inbound_connection( &mut self, @@ -116,13 +129,11 @@ where }) => { if let Some(sessions) = self.peers_pending_dial_with_sessions.remove(&peer_id) { self.pending_events.extend(sessions.iter().map(|outbound_session_id| { - ToSwarm::GenerateEvent(Event::NotifyStreamedBytes( - streamed_bytes::behaviour::FromOtherBehaviour::SessionAssigned { - outbound_session_id: *outbound_session_id, - peer_id, - connection_id, - }, - )) + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::SessionAssigned { + outbound_session_id: *outbound_session_id, + peer_id, + connection_id, + }) })); self.peers .get_mut(&peer_id) @@ -130,17 +141,15 @@ where "in case we are waiting for a connection established event we assum \ the peer is known to the peer manager", ) - .set_connection_id(Some(connection_id)); + .add_connection_id(connection_id); } else if !self.peers.contains_key(&peer_id) { let mut peer = P::new(peer_id, endpoint.get_remote_address().clone()); - peer.set_connection_id(Some(connection_id)); + peer.add_connection_id(connection_id); self.add_peer(peer); if !self.more_peers_needed() { // TODO: consider how and in which cases we resume discovery self.pending_events.push(libp2p::swarm::ToSwarm::GenerateEvent( - Event::NotifyDiscovery( - discovery::FromOtherBehaviourEvent::PauseDiscovery, - ), + ToOtherBehaviourEvent::PauseDiscovery, )) } } @@ -151,22 +160,16 @@ where .. }) => { if let Some(peer) = self.peers.get_mut(&peer_id) { - if let Some(known_connection_id) = peer.connection_id() { - if known_connection_id == connection_id { - peer.set_connection_id(None); - } else { - error!( - "Connection closed event for a peer with a different connection \ - id. known connection id: {}, emitted connection id: {}", - known_connection_id, connection_id - ); - return; - } + let known_connection_ids = peer.connection_ids(); + if known_connection_ids.contains(&connection_id) { + peer.remove_connection_id(connection_id); } else { - error!("Connection closed event for a peer without a connection id"); - return; + error!( + "Connection closed event for a peer with a different connection id. \ + known connection ids: {:?}, emitted connection id: {}", + known_connection_ids, connection_id + ); } - peer.set_connection_id(None); } } _ => {} diff --git a/crates/papyrus_network/src/peer_manager/mod.rs b/crates/papyrus_network/src/peer_manager/mod.rs index c546e5d15c3..99da393d6e7 100644 --- a/crates/papyrus_network/src/peer_manager/mod.rs +++ b/crates/papyrus_network/src/peer_manager/mod.rs @@ -6,11 +6,11 @@ use libp2p::swarm::ToSwarm; use libp2p::PeerId; use tracing::info; -use self::behaviour_impl::Event; +pub use self::behaviour_impl::ToOtherBehaviourEvent; use self::peer::PeerTrait; use crate::mixed_behaviour::BridgedBehaviour; -use crate::streamed_bytes::OutboundSessionId; -use crate::{mixed_behaviour, streamed_bytes}; +use crate::sqmr::OutboundSessionId; +use crate::{mixed_behaviour, sqmr}; pub(crate) mod behaviour_impl; pub(crate) mod peer; @@ -29,7 +29,7 @@ pub struct PeerManager { session_to_peer_map: HashMap, config: PeerManagerConfig, last_peer_index: usize, - pending_events: Vec>>, + pending_events: Vec>>, peers_pending_dial_with_sessions: HashMap>, sessions_received_when_no_peers: Vec, } @@ -50,11 +50,6 @@ pub(crate) enum PeerManagerError { PeerIsBlocked(PeerId), } -#[derive(Debug)] -pub enum FromOtherBehaviour { - RequestPeerAssignment { outbound_session_id: OutboundSessionId }, -} - impl Default for PeerManagerConfig { fn default() -> Self { Self { target_num_for_peers: 100, blacklist_timeout: Duration::max_value() } @@ -93,7 +88,7 @@ where self.peers.get_mut(&peer_id) } - // TODO(shahak): Remove return value and use FromOtherBehaviour in tests. + // TODO(shahak): Remove return value and use events in tests. fn assign_peer_to_session(&mut self, outbound_session_id: OutboundSessionId) -> Option { // TODO: consider moving this logic to be async (on a different tokio task) // until then we can return the assignment even if we use events for the notification. @@ -113,18 +108,20 @@ where peer.map(|(peer_id, peer)| { // TODO: consider not allowing reassignment of the same session self.session_to_peer_map.insert(outbound_session_id, *peer_id); - if let Some(connection_id) = peer.connection_id() { + let peer_connection_ids = peer.connection_ids(); + if !peer_connection_ids.is_empty() { + let connection_id = peer_connection_ids[0]; info!( "Session {:?} assigned to peer {:?} with connection id: {:?}", outbound_session_id, peer_id, connection_id ); - self.pending_events.push(ToSwarm::GenerateEvent(Event::NotifyStreamedBytes( - streamed_bytes::behaviour::FromOtherBehaviour::SessionAssigned { + self.pending_events.push(ToSwarm::GenerateEvent( + ToOtherBehaviourEvent::SessionAssigned { outbound_session_id, peer_id: *peer_id, connection_id, }, - ))); + )); } else { // In case we have a race condition where the connection is closed after we added to // the pending list, the reciever will get an error and will need to ask for @@ -144,7 +141,7 @@ where }) } - fn report_peer( + pub(crate) fn report_peer( &mut self, peer_id: PeerId, reason: ReputationModifier, @@ -183,28 +180,20 @@ where } } -impl From for mixed_behaviour::Event { - fn from(event: Event) -> Self { - match event { - Event::NotifyStreamedBytes(event) => { - Self::InternalEvent(mixed_behaviour::InternalEvent::NotifyStreamedBytes(event)) - } - Event::NotifyDiscovery(event) => { - Self::InternalEvent(mixed_behaviour::InternalEvent::NotifyDiscovery(event)) - } - } +impl From for mixed_behaviour::Event { + fn from(event: ToOtherBehaviourEvent) -> Self { + Self::ToOtherBehaviourEvent(mixed_behaviour::ToOtherBehaviourEvent::PeerManager(event)) } } impl BridgedBehaviour for PeerManager

{ - fn on_other_behaviour_event(&mut self, event: mixed_behaviour::InternalEvent) { - let mixed_behaviour::InternalEvent::NotifyPeerManager(event) = event else { + fn on_other_behaviour_event(&mut self, event: &mixed_behaviour::ToOtherBehaviourEvent) { + let mixed_behaviour::ToOtherBehaviourEvent::Sqmr( + sqmr::ToOtherBehaviourEvent::RequestPeerAssignment { outbound_session_id }, + ) = event + else { return; }; - match event { - FromOtherBehaviour::RequestPeerAssignment { outbound_session_id } => { - self.assign_peer_to_session(outbound_session_id); - } - } + self.assign_peer_to_session(*outbound_session_id); } } diff --git a/crates/papyrus_network/src/peer_manager/peer.rs b/crates/papyrus_network/src/peer_manager/peer.rs index 1edbf7d54b9..0d7711869b6 100644 --- a/crates/papyrus_network/src/peer_manager/peer.rs +++ b/crates/papyrus_network/src/peer_manager/peer.rs @@ -23,10 +23,11 @@ pub trait PeerTrait { fn is_blocked(&self) -> bool; - // TODO: add support for multiple connections for a peer - fn connection_id(&self) -> Option; + fn connection_ids(&self) -> &Vec; - fn set_connection_id(&mut self, connection_id: Option); + fn add_connection_id(&mut self, connection_id: ConnectionId); + + fn remove_connection_id(&mut self, connection_id: ConnectionId); } #[derive(Clone)] @@ -35,7 +36,7 @@ pub struct Peer { multiaddr: Multiaddr, timed_out_until: Option>, timeout_duration: Option, - connection_id: Option, + connection_ids: Vec, } impl PeerTrait for Peer { @@ -45,7 +46,7 @@ impl PeerTrait for Peer { multiaddr, timeout_duration: None, timed_out_until: None, - connection_id: None, + connection_ids: Vec::new(), } } @@ -78,11 +79,15 @@ impl PeerTrait for Peer { } } - fn connection_id(&self) -> Option { - self.connection_id + fn connection_ids(&self) -> &Vec { + &self.connection_ids + } + + fn add_connection_id(&mut self, connection_id: ConnectionId) { + self.connection_ids.push(connection_id); } - fn set_connection_id(&mut self, connection_id: Option) { - self.connection_id = connection_id; + fn remove_connection_id(&mut self, connection_id: ConnectionId) { + self.connection_ids.retain(|&id| id != connection_id); } } diff --git a/crates/papyrus_network/src/peer_manager/test.rs b/crates/papyrus_network/src/peer_manager/test.rs index e65cb2667d0..5807163b636 100644 --- a/crates/papyrus_network/src/peer_manager/test.rs +++ b/crates/papyrus_network/src/peer_manager/test.rs @@ -1,3 +1,5 @@ +// TODO(shahak): Add tests for multiple connection ids + use core::{panic, time}; use assert_matches::assert_matches; @@ -9,11 +11,10 @@ use libp2p::{Multiaddr, PeerId}; use mockall::predicate::eq; use tokio::time::sleep; -use super::behaviour_impl::Event; +use super::behaviour_impl::ToOtherBehaviourEvent; use crate::peer_manager::peer::{MockPeerTrait, Peer, PeerTrait}; use crate::peer_manager::{PeerManager, PeerManagerConfig, ReputationModifier}; -use crate::streamed_bytes::OutboundSessionId; -use crate::{discovery, streamed_bytes}; +use crate::sqmr::OutboundSessionId; #[test] fn peer_assignment_round_robin() { @@ -54,13 +55,11 @@ fn peer_assignment_round_robin() { // check assignment events for event in peer_manager.pending_events { - let ToSwarm::GenerateEvent(Event::NotifyStreamedBytes( - streamed_bytes::behaviour::FromOtherBehaviour::SessionAssigned { - outbound_session_id, - peer_id, - connection_id, - }, - )) = event + let ToSwarm::GenerateEvent(ToOtherBehaviourEvent::SessionAssigned { + outbound_session_id, + peer_id, + connection_id, + }) = event else { continue; }; @@ -68,15 +67,15 @@ fn peer_assignment_round_robin() { match outbound_session_id { OutboundSessionId { value: 1 } => { assert_eq!(peer_id, peer1.peer_id()); - assert_eq!(connection_id, peer1.connection_id().unwrap()) + assert_eq!(connection_id, *peer1.connection_ids().first().unwrap()) } OutboundSessionId { value: 2 } => { assert_eq!(peer_id, peer2.peer_id()); - assert_eq!(connection_id, peer2.connection_id().unwrap()); + assert_eq!(connection_id, *peer2.connection_ids().first().unwrap()); } OutboundSessionId { value: 3 } => { assert_eq!(peer_id, peer1.peer_id()); - assert_eq!(connection_id, peer1.connection_id().unwrap()); + assert_eq!(connection_id, *peer1.connection_ids().first().unwrap()); } _ => panic!("Unexpected outbound_session_id: {:?}", outbound_session_id), } @@ -84,15 +83,15 @@ fn peer_assignment_round_robin() { match outbound_session_id { OutboundSessionId { value: 1 } => { assert_eq!(peer_id, peer2.peer_id()); - assert_eq!(connection_id, peer2.connection_id().unwrap()); + assert_eq!(connection_id, *peer2.connection_ids().first().unwrap()); } OutboundSessionId { value: 2 } => { assert_eq!(peer_id, peer1.peer_id()); - assert_eq!(connection_id, peer1.connection_id().unwrap()); + assert_eq!(connection_id, *peer1.connection_ids().first().unwrap()); } OutboundSessionId { value: 3 } => { assert_eq!(peer_id, peer2.peer_id()); - assert_eq!(connection_id, peer2.connection_id().unwrap()); + assert_eq!(connection_id, *peer2.connection_ids().first().unwrap()); } _ => panic!("Unexpected outbound_session_id: {:?}", outbound_session_id), } @@ -121,13 +120,12 @@ fn peer_assignment_no_peers() { assert_eq!(peer_manager.pending_events.len(), 1); assert_matches!( peer_manager.pending_events.first().unwrap(), - ToSwarm::GenerateEvent(Event::NotifyStreamedBytes( - streamed_bytes::behaviour::FromOtherBehaviour::SessionAssigned { + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::SessionAssigned { outbound_session_id: event_outbound_session_id, peer_id: event_peer_id, connection_id: event_connection_id, } - )) if outbound_session_id == *event_outbound_session_id && + ) if outbound_session_id == *event_outbound_session_id && peer_id == *event_peer_id && connection_id == *event_connection_id ); @@ -314,7 +312,7 @@ fn create_mock_peer( .return_once(|_| ()) .in_sequence(&mut mockall_seq); } - peer.expect_connection_id().return_const(connection_id); + peer.expect_connection_ids().return_const(connection_id.map(|x| vec![x]).unwrap_or_default()); (peer, peer_id) } @@ -394,7 +392,7 @@ async fn flow_test_assign_non_connected_peer() { let (mut peer, peer_id) = create_mock_peer(config.blacklist_timeout, false, None); peer.expect_is_blocked().times(1).return_const(false); peer.expect_multiaddr().return_const(Multiaddr::empty()); - peer.expect_set_connection_id().times(1).return_const(()); + peer.expect_add_connection_id().times(1).return_const(()); // Add the mock peer to the peer manager peer_manager.add_peer(peer); @@ -423,10 +421,10 @@ async fn flow_test_assign_non_connected_peer() { }, )); - // Expect NotifyStreamedBytes event + // Expect SessionAssigned event assert_matches!( poll_fn(|cx| peer_manager.poll(cx)).await, - ToSwarm::GenerateEvent(Event::NotifyStreamedBytes(_)) + ToSwarm::GenerateEvent(ToOtherBehaviourEvent::SessionAssigned { .. }) ); } @@ -484,10 +482,7 @@ fn no_more_peers_needed_stops_discovery() { // Check that the discovery pause event emitted for event in peer_manager.pending_events { - if let ToSwarm::GenerateEvent(Event::NotifyDiscovery( - discovery::FromOtherBehaviourEvent::PauseDiscovery, - )) = event - { + if let ToSwarm::GenerateEvent(ToOtherBehaviourEvent::PauseDiscovery) = event { return; } } diff --git a/crates/papyrus_network/src/protobuf_messages/mod.rs b/crates/papyrus_network/src/protobuf_messages/mod.rs deleted file mode 100644 index 776833eccd1..00000000000 --- a/crates/papyrus_network/src/protobuf_messages/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod protobuf { - include!(concat!(env!("OUT_DIR"), "/_.rs")); -} diff --git a/crates/papyrus_network/src/streamed_bytes/behaviour.rs b/crates/papyrus_network/src/sqmr/behaviour.rs similarity index 85% rename from crates/papyrus_network/src/streamed_bytes/behaviour.rs rename to crates/papyrus_network/src/sqmr/behaviour.rs index 449b1d247c2..fc8cb19f8e6 100644 --- a/crates/papyrus_network/src/streamed_bytes/behaviour.rs +++ b/crates/papyrus_network/src/sqmr/behaviour.rs @@ -43,8 +43,6 @@ pub enum SessionError { IOError(#[from] io::Error), #[error("Remote peer doesn't support the given protocol.")] RemoteDoesntSupportProtocol, - #[error("In an inbound session, remote peer sent data after sending the query.")] - OtherOutboundPeerSentData, // If there's a connection with a single session and it was closed because of another reason, // we might get ConnectionClosed instead of that reason because the swarm automatically closes // a connection that has no sessions. If this is a problem, set the swarm's @@ -81,10 +79,6 @@ impl From> for GenericEvent { } => { Self::SessionFailed { session_id, error: SessionError::RemoteDoesntSupportProtocol } } - GenericEvent::SessionFailed { - session_id, - error: HandlerSessionError::OtherOutboundPeerSentData, - } => Self::SessionFailed { session_id, error: SessionError::OtherOutboundPeerSentData }, GenericEvent::SessionFinishedSuccessfully { session_id } => { Self::SessionFinishedSuccessfully { session_id } } @@ -95,24 +89,14 @@ impl From> for GenericEvent { pub type ExternalEvent = GenericEvent; #[derive(Debug)] -pub enum ToOtherBehaviour { - NotifyPeerManager(peer_manager::FromOtherBehaviour), +pub enum ToOtherBehaviourEvent { + RequestPeerAssignment { outbound_session_id: OutboundSessionId }, } #[derive(Debug)] pub enum Event { External(ExternalEvent), - ToOtherBehaviour(ToOtherBehaviour), -} - -#[derive(Debug)] -#[cfg_attr(test, derive(PartialEq))] -pub enum FromOtherBehaviour { - SessionAssigned { - outbound_session_id: OutboundSessionId, - peer_id: PeerId, - connection_id: ConnectionId, - }, + ToOtherBehaviourEvent(ToOtherBehaviourEvent), } #[derive(thiserror::Error, Debug)] @@ -194,10 +178,8 @@ impl Behaviour { self.outbound_sessions_pending_peer_assignment .insert(outbound_session_id, (query, protocol_name)); info!("Requesting peer assignment for outbound session: {:?}.", outbound_session_id); - self.add_event_to_queue(ToSwarm::GenerateEvent(Event::ToOtherBehaviour( - ToOtherBehaviour::NotifyPeerManager( - peer_manager::FromOtherBehaviour::RequestPeerAssignment { outbound_session_id }, - ), + self.add_event_to_queue(ToSwarm::GenerateEvent(Event::ToOtherBehaviourEvent( + ToOtherBehaviourEvent::RequestPeerAssignment { outbound_session_id }, ))); outbound_session_id @@ -381,36 +363,39 @@ impl NetworkBehaviour for Behaviour { } impl BridgedBehaviour for Behaviour { - fn on_other_behaviour_event(&mut self, event: mixed_behaviour::InternalEvent) { - let mixed_behaviour::InternalEvent::NotifyStreamedBytes(event) = event else { + fn on_other_behaviour_event(&mut self, event: &mixed_behaviour::ToOtherBehaviourEvent) { + let mixed_behaviour::ToOtherBehaviourEvent::PeerManager( + peer_manager::ToOtherBehaviourEvent::SessionAssigned { + outbound_session_id, + peer_id, + connection_id, + }, + ) = event + else { return; }; - match event { - FromOtherBehaviour::SessionAssigned { outbound_session_id, peer_id, connection_id } => { - self.session_id_to_peer_id_and_connection_id - .insert(outbound_session_id.into(), (peer_id, connection_id)); - - let Some((query, protocol_name)) = - self.outbound_sessions_pending_peer_assignment.remove(&outbound_session_id) - else { - error!( - "Outbound session assigned peer but it isn't in \ - outbound_sessions_pending_peer_assignment. Not running query." - ); - return; - }; - - self.add_event_to_queue(ToSwarm::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection_id), - event: RequestFromBehaviourEvent::CreateOutboundSession { - query, - outbound_session_id, - protocol_name, - }, - }); - } - } + self.session_id_to_peer_id_and_connection_id + .insert((*outbound_session_id).into(), (*peer_id, *connection_id)); + + let Some((query, protocol_name)) = + self.outbound_sessions_pending_peer_assignment.remove(outbound_session_id) + else { + error!( + "Outbound session assigned peer but it isn't in \ + outbound_sessions_pending_peer_assignment. Not running query." + ); + return; + }; + + self.add_event_to_queue(ToSwarm::NotifyHandler { + peer_id: *peer_id, + handler: NotifyHandler::One(*connection_id), + event: RequestFromBehaviourEvent::CreateOutboundSession { + query, + outbound_session_id: *outbound_session_id, + protocol_name, + }, + }); } } @@ -418,10 +403,10 @@ impl From for mixed_behaviour::Event { fn from(event: Event) -> Self { match event { Event::External(external_event) => { - Self::ExternalEvent(mixed_behaviour::ExternalEvent::StreamedBytes(external_event)) + Self::ExternalEvent(mixed_behaviour::ExternalEvent::Sqmr(external_event)) } - Event::ToOtherBehaviour(ToOtherBehaviour::NotifyPeerManager(event)) => { - Self::InternalEvent(mixed_behaviour::InternalEvent::NotifyPeerManager(event)) + Event::ToOtherBehaviourEvent(event) => { + Self::ToOtherBehaviourEvent(mixed_behaviour::ToOtherBehaviourEvent::Sqmr(event)) } } } diff --git a/crates/papyrus_network/src/streamed_bytes/behaviour_test.rs b/crates/papyrus_network/src/sqmr/behaviour_test.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/behaviour_test.rs rename to crates/papyrus_network/src/sqmr/behaviour_test.rs diff --git a/crates/papyrus_network/src/streamed_bytes/flow_test.rs b/crates/papyrus_network/src/sqmr/flow_test.rs similarity index 98% rename from crates/papyrus_network/src/streamed_bytes/flow_test.rs rename to crates/papyrus_network/src/sqmr/flow_test.rs index 012394dd1ff..9881f1ff42c 100644 --- a/crates/papyrus_network/src/streamed_bytes/flow_test.rs +++ b/crates/papyrus_network/src/sqmr/flow_test.rs @@ -11,7 +11,8 @@ use libp2p::{PeerId, Swarm}; use super::behaviour::{Behaviour, Event, ExternalEvent}; use super::messages::with_length_prefix; use super::{Bytes, Config, InboundSessionId, OutboundSessionId, SessionId}; -use crate::test_utils::{create_fully_connected_swarms_stream, StreamHashMap}; +use crate::test_utils::create_fully_connected_swarms_stream; +use crate::utils::StreamHashMap; const NUM_PEERS: usize = 3; const NUM_MESSAGES_PER_SESSION: usize = 5; diff --git a/crates/papyrus_network/src/streamed_bytes/handler.rs b/crates/papyrus_network/src/sqmr/handler.rs similarity index 99% rename from crates/papyrus_network/src/streamed_bytes/handler.rs rename to crates/papyrus_network/src/sqmr/handler.rs index 71a98e4787b..51d8f008a21 100644 --- a/crates/papyrus_network/src/streamed_bytes/handler.rs +++ b/crates/papyrus_network/src/sqmr/handler.rs @@ -67,9 +67,6 @@ pub enum SessionError { IOError(#[from] io::Error), #[error("Remote peer doesn't support the given protocol.")] RemoteDoesntSupportProtocol, - // TODO(shahak) erase this. - #[error("In an inbound session, remote peer sent data after sending the query.")] - OtherOutboundPeerSentData, } type HandlerEvent = ConnectionHandlerEvent< diff --git a/crates/papyrus_network/src/streamed_bytes/handler/inbound_session.rs b/crates/papyrus_network/src/sqmr/handler/inbound_session.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/handler/inbound_session.rs rename to crates/papyrus_network/src/sqmr/handler/inbound_session.rs diff --git a/crates/papyrus_network/src/streamed_bytes/handler_test.rs b/crates/papyrus_network/src/sqmr/handler_test.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/handler_test.rs rename to crates/papyrus_network/src/sqmr/handler_test.rs diff --git a/crates/papyrus_network/src/streamed_bytes/messages.rs b/crates/papyrus_network/src/sqmr/messages.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/messages.rs rename to crates/papyrus_network/src/sqmr/messages.rs diff --git a/crates/papyrus_network/src/streamed_bytes/messages_test.rs b/crates/papyrus_network/src/sqmr/messages_test.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/messages_test.rs rename to crates/papyrus_network/src/sqmr/messages_test.rs diff --git a/crates/papyrus_network/src/streamed_bytes/mod.rs b/crates/papyrus_network/src/sqmr/mod.rs similarity index 97% rename from crates/papyrus_network/src/streamed_bytes/mod.rs rename to crates/papyrus_network/src/sqmr/mod.rs index 7926c7722f7..8d768db0678 100644 --- a/crates/papyrus_network/src/streamed_bytes/mod.rs +++ b/crates/papyrus_network/src/sqmr/mod.rs @@ -8,7 +8,7 @@ mod flow_test; use std::time::Duration; -pub use behaviour::Behaviour; +pub use behaviour::{Behaviour, ToOtherBehaviourEvent}; use derive_more::Display; use libp2p::swarm::StreamProtocol; use libp2p::PeerId; diff --git a/crates/papyrus_network/src/streamed_bytes/protocol.rs b/crates/papyrus_network/src/sqmr/protocol.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/protocol.rs rename to crates/papyrus_network/src/sqmr/protocol.rs diff --git a/crates/papyrus_network/src/streamed_bytes/protocol_test.rs b/crates/papyrus_network/src/sqmr/protocol_test.rs similarity index 100% rename from crates/papyrus_network/src/streamed_bytes/protocol_test.rs rename to crates/papyrus_network/src/sqmr/protocol_test.rs diff --git a/crates/papyrus_network/src/test_utils/mod.rs b/crates/papyrus_network/src/test_utils/mod.rs index 4eeff241112..2cf5bd4218b 100644 --- a/crates/papyrus_network/src/test_utils/mod.rs +++ b/crates/papyrus_network/src/test_utils/mod.rs @@ -1,24 +1,22 @@ mod get_stream; -use std::collections::hash_map::{Keys, ValuesMut}; -use std::collections::{HashMap, HashSet}; use std::fmt::Debug; -use std::hash::Hash; use std::pin::Pin; use std::task::{ready, Context, Poll}; use std::time::Duration; use futures::future::Future; use futures::pin_mut; -use futures::stream::{Stream as StreamTrait, StreamExt}; +use futures::stream::Stream as StreamTrait; use libp2p::swarm::{NetworkBehaviour, StreamProtocol, Swarm, SwarmEvent}; use libp2p::{PeerId, Stream}; use libp2p_swarm_test::SwarmExt; use tokio::sync::Mutex; use tokio::task::JoinHandle; -use tokio_stream::StreamExt as TokioStreamExt; +use tokio_stream::StreamExt; -use crate::streamed_bytes::Bytes; +use crate::sqmr::Bytes; +use crate::utils::StreamHashMap; /// Create two streams that are connected to each other. Return them and a join handle for a thread /// that will perform the sends between the streams (this thread will run forever so it shouldn't @@ -32,15 +30,13 @@ pub(crate) async fn get_connected_streams() -> (Stream, Stream, JoinHandle<()>) swarm1.connect(&mut swarm2).await; let merged_swarm = swarm1.merge(swarm2); - let mut filtered_swarm = TokioStreamExt::filter_map(merged_swarm, |event| { + let mut filtered_swarm = merged_swarm.filter_map(|event| { if let SwarmEvent::Behaviour(stream) = event { Some(stream) } else { None } }); ( - TokioStreamExt::next(&mut filtered_swarm).await.unwrap(), - TokioStreamExt::next(&mut filtered_swarm).await.unwrap(), - tokio::task::spawn(async move { - while TokioStreamExt::next(&mut filtered_swarm).await.is_some() {} - }), + filtered_swarm.next().await.unwrap(), + filtered_swarm.next().await.unwrap(), + tokio::task::spawn(async move { while filtered_swarm.next().await.is_some() {} }), ) } @@ -48,7 +44,7 @@ pub(crate) fn dummy_data() -> Vec { vec![vec![1u8], vec![2u8, 3u8], vec![4u8, 5u8, 6u8]] } -impl crate::streamed_bytes::Config { +impl crate::sqmr::Config { pub fn get_test_config() -> Self { Self { session_timeout: Duration::MAX, @@ -57,58 +53,6 @@ impl crate::streamed_bytes::Config { } } -// This is an implementation of `StreamMap` from tokio_stream. The reason we're implementing it -// ourselves is that the implementation in tokio_stream requires that the values implement the -// Stream trait from tokio_stream and not from futures. -pub(crate) struct StreamHashMap { - map: HashMap, - finished_streams: HashSet, -} - -impl StreamHashMap { - pub fn new(map: HashMap) -> Self { - Self { map, finished_streams: Default::default() } - } - - pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { - self.map.values_mut() - } - - pub fn keys(&self) -> Keys<'_, K, V> { - self.map.keys() - } - - pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { - self.map.get_mut(key) - } -} - -impl StreamTrait for StreamHashMap { - type Item = (K, ::Item); - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let unpinned_self = Pin::into_inner(self); - let mut finished = true; - for (key, stream) in &mut unpinned_self.map { - match stream.poll_next_unpin(cx) { - Poll::Ready(Some(value)) => { - return Poll::Ready(Some((key.clone(), value))); - } - Poll::Ready(None) => { - unpinned_self.finished_streams.insert(key.clone()); - } - Poll::Pending => { - finished = false; - } - } - } - if finished { - return Poll::Ready(None); - } - Poll::Pending - } -} - /// Create num_swarms swarms and connect each pair of swarms. Return them as a combined stream of /// events. pub(crate) async fn create_fully_connected_swarms_stream( diff --git a/crates/papyrus_network/src/utils.rs b/crates/papyrus_network/src/utils.rs new file mode 100644 index 00000000000..d9a9f418d9b --- /dev/null +++ b/crates/papyrus_network/src/utils.rs @@ -0,0 +1,67 @@ +use std::collections::hash_map::{Keys, ValuesMut}; +use std::collections::{HashMap, HashSet}; +use std::hash::Hash; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use futures::stream::{Stream, StreamExt}; + +// This is an implementation of `StreamMap` from tokio_stream. The reason we're implementing it +// ourselves is that the implementation in tokio_stream requires that the values implement the +// Stream trait from tokio_stream and not from futures. +pub(crate) struct StreamHashMap { + map: HashMap, + finished_streams: HashSet, +} + +impl StreamHashMap { + #[allow(dead_code)] + pub fn new(map: HashMap) -> Self { + Self { map, finished_streams: Default::default() } + } + + #[allow(dead_code)] + pub fn values_mut(&mut self) -> ValuesMut<'_, K, V> { + self.map.values_mut() + } + + #[allow(dead_code)] + pub fn keys(&self) -> Keys<'_, K, V> { + self.map.keys() + } + + #[allow(dead_code)] + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.map.get_mut(key) + } + + pub fn insert(&mut self, key: K, value: V) -> Option { + self.map.insert(key, value) + } +} + +impl Stream for StreamHashMap { + type Item = (K, ::Item); + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let unpinned_self = Pin::into_inner(self); + let mut finished = true; + for (key, stream) in &mut unpinned_self.map { + match stream.poll_next_unpin(cx) { + Poll::Ready(Some(value)) => { + return Poll::Ready(Some((key.clone(), value))); + } + Poll::Ready(None) => { + unpinned_self.finished_streams.insert(key.clone()); + } + Poll::Pending => { + finished = false; + } + } + } + if finished { + return Poll::Ready(None); + } + Poll::Pending + } +} diff --git a/crates/papyrus_node/Cargo.toml b/crates/papyrus_node/Cargo.toml index 3209bb96c51..5a77e6fbffc 100644 --- a/crates/papyrus_node/Cargo.toml +++ b/crates/papyrus_node/Cargo.toml @@ -27,19 +27,21 @@ itertools.workspace = true jsonrpsee = { workspace = true, features = ["full"] } libmdbx = { workspace = true, features = ["lifetimed-bytes"] } lazy_static.workspace = true +once_cell.workspace = true papyrus_base_layer = { path = "../papyrus_base_layer", version = "0.4.0-dev.2" } papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } papyrus_common = { path = "../papyrus_common", version = "0.4.0-dev.2" } papyrus_monitoring_gateway = { path = "../papyrus_monitoring_gateway", version = "0.4.0-dev.2" } papyrus_network = { path = "../papyrus_network", version = "0.4.0-dev.2" } papyrus_p2p_sync = { path = "../papyrus_p2p_sync", version = "0.4.0-dev.2" } +papyrus_protobuf = { path = "../papyrus_protobuf", version = "0.4.0-dev.2" } papyrus_rpc = { path = "../papyrus_rpc", version = "0.4.0-dev.2", optional = true } papyrus_storage = { path = "../papyrus_storage", version = "0.4.0-dev.2" } papyrus_sync = { path = "../papyrus_sync", version = "0.4.0-dev.2" } reqwest = { workspace = true, features = ["json", "blocking"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api = { workspace = true, features = ["testing"] } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } starknet_client = { path = "../starknet_client" } strum.workspace = true thiserror.workspace = true @@ -61,4 +63,4 @@ metrics-exporter-prometheus.workspace = true pretty_assertions.workspace = true insta = { workspace = true, features = ["json"] } tempfile.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/papyrus_node/examples/get_transaction_hash.rs b/crates/papyrus_node/examples/get_transaction_hash.rs index d47ac47a7e7..d189d112228 100644 --- a/crates/papyrus_node/examples/get_transaction_hash.rs +++ b/crates/papyrus_node/examples/get_transaction_hash.rs @@ -1,7 +1,10 @@ use std::cmp::min; use std::collections::{BTreeMap, HashSet}; +use std::sync::Mutex; use clap::{Arg, Command}; +use futures::future::join_all; +use once_cell::sync::OnceCell; use papyrus_common::transaction_hash::{ get_transaction_hash, MAINNET_TRANSACTION_HASH_WITH_VERSION, @@ -16,11 +19,14 @@ use strum::IntoEnumIterator; const DEFAULT_TRANSACTION_HASH_PATH: &str = "crates/papyrus_common/resources/transaction_hash_new.json"; +const MAX_CONCURRENT_REQUESTS: u64 = 100; + struct CliParams { node_url: String, iteration_increments: u64, file_path: String, deprecated: bool, + concurrent_requests: u64, } /// The start_block and end_block arguments are mandatory and define the block range to dump, @@ -49,6 +55,13 @@ fn get_cli_params() -> CliParams { .default_value("1") .help("The iteration increments used to query the node."), ) + .arg( + Arg::new("concurrent_requests") + .short('c') + .long("concurrent_requests") + .default_value(MAX_CONCURRENT_REQUESTS.to_string()) + .help("The maximum number of concurrent requests."), + ) .arg( Arg::new("deprecated") .short('d') @@ -67,12 +80,17 @@ fn get_cli_params() -> CliParams { .expect("Failed parsing iteration_increments") .parse::() .expect("Failed parsing iteration_increments"); + let concurrent_requests = matches + .get_one::("concurrent_requests") + .expect("Failed parsing concurrent_requests") + .parse::() + .expect("Failed parsing concurrent_requests"); let deprecated = matches .get_one::("deprecated") .expect("Failed parsing deprecated") .parse::() .expect("Failed parsing deprecated"); - CliParams { node_url, iteration_increments, file_path, deprecated } + CliParams { node_url, iteration_increments, file_path, deprecated, concurrent_requests } } // Define a tuple struct to hold transaction type and version @@ -105,54 +123,109 @@ fn get_all_transaction_types() -> HashSet { async fn main() -> Result<(), Box> { println!("Starting Starknet transaction hash dump."); - let CliParams { node_url, iteration_increments, file_path, deprecated } = get_cli_params(); + let CliParams { node_url, iteration_increments, file_path, deprecated, concurrent_requests } = + get_cli_params(); let file = std::fs::File::create(file_path)?; let mut writer = std::io::BufWriter::new(&file); - let mut transaction_types = get_all_transaction_types(); - let mut acumulated_transactions = vec![]; - + static TRANSACTION_TYPES: OnceCell>> = + OnceCell::>>::new(); + let _ = TRANSACTION_TYPES.set(Mutex::new(get_all_transaction_types())); + static ACUMULATED_TRANSACTIONS: OnceCell>>> = + OnceCell::>>>::new(); + let _ = ACUMULATED_TRANSACTIONS.set(Mutex::new(vec![])); let client = reqwest::Client::new(); + + // TODO(Eitan): Fix the block number to start from min of deprecated and synced block number let mut block_number: u64 = if deprecated { MAINNET_TRANSACTION_HASH_WITH_VERSION.0 } else { get_current_block_number_via_rpc(&client, node_url.clone()).await? }; - while block_number > 0 && !transaction_types.is_empty() { - println!("Processing block number: {}", block_number); - let block_transactions = - get_block_transactions_via_rpc(&client, node_url.clone(), block_number).await?; + while block_number > 0 + && !TRANSACTION_TYPES + .get() + .expect("Couldn't get transaction types") + .lock() + .expect("Couldn't lock transaction types") + .is_empty() + { + let mut handles = vec![]; - // For each transaction in the block, check if it's a unique transaction type and version - // and add it to the acumulated_transactions - for transaction in block_transactions.iter().cloned() { - let transaction_info = parse_transaction_info_from_value(&transaction); - if transaction_types.remove(&transaction_info) { - let unique_transaction = construct_transaction_from_value( - transaction.clone(), - &transaction_info.transaction_type, - &transaction_info.transaction_version, - )?; - let transaction_hash = transaction["transaction_hash"] - .as_str() - .expect("Couldn't parse 'transaction_hash' from json transaction") - .to_string(); + while block_number > 0 + && !TRANSACTION_TYPES + .get() + .expect("Couldn't get transaction types") + .lock() + .expect("Couldn't lock transaction types") + .is_empty() + && handles.len() < concurrent_requests as usize + { + let client_ref = client.clone(); + let node_url_ref = node_url.clone(); - let transaction_map = create_map_of_transaction( - &unique_transaction, - block_number, - transaction_hash, - deprecated, - ); - acumulated_transactions.push(transaction_map); - } - } + let handle = async move { + println!("Processing block number: {}", block_number); + let block_transactions = + get_block_transactions_via_rpc(&client_ref, node_url_ref.clone(), block_number) + .await + .unwrap_or_else(|_| { + println!( + "Failed to get block transactions for block number: {}", + block_number + ); + vec![] + }); + + // For each transaction in the block, check if it's a unique transaction type and + // version and add it to the acumulated_transactions + for transaction in block_transactions.iter().cloned() { + let transaction_info = parse_transaction_info_from_value(&transaction); + let mut transaction_types_handle = TRANSACTION_TYPES + .get() + .expect("Couldn't get transaction types") + .lock() + .expect("Couldn't lock transaction types"); + if transaction_types_handle.remove(&transaction_info) { + let unique_transaction = construct_transaction_from_value( + transaction.clone(), + &transaction_info.transaction_type, + &transaction_info.transaction_version, + ) + .expect("Couldn't construct transaction from value"); + let transaction_hash = transaction["transaction_hash"] + .as_str() + .expect("Couldn't parse 'transaction_hash' from json transaction") + .to_string(); - // Decrement the block number by the iteration_increments - block_number -= min(iteration_increments, block_number); + let transaction_map = create_map_of_transaction( + &unique_transaction, + block_number, + transaction_hash, + deprecated, + ); + ACUMULATED_TRANSACTIONS + .get() + .expect("Couldn't get acumulated transactions") + .lock() + .expect("Couldn't lock acumulated transactions") + .push(transaction_map); + } + } + }; + // Decrement the block number by the iteration_increments + block_number -= min(iteration_increments, block_number); + handles.push(handle); + } + // Wait for all the spawned tasks to finish + join_all(handles).await; } - to_writer_pretty(&mut writer, &acumulated_transactions)?; + + to_writer_pretty( + &mut writer, + &ACUMULATED_TRANSACTIONS.get().expect("Couldn't get acumulated transactions"), + )?; println!("Transaction hash dump completed."); Ok(()) } diff --git a/crates/papyrus_node/src/config/config_test.rs b/crates/papyrus_node/src/config/config_test.rs index 1c8d08f9ad1..af308167462 100644 --- a/crates/papyrus_node/src/config/config_test.rs +++ b/crates/papyrus_node/src/config/config_test.rs @@ -15,11 +15,11 @@ use papyrus_config::dumping::SerializeConfig; use papyrus_config::presentation::get_config_presentation; use papyrus_config::{SerializationType, SerializedContent, SerializedParam}; use papyrus_monitoring_gateway::MonitoringGatewayConfig; +use papyrus_test_utils::get_absolute_path; use pretty_assertions::assert_eq; use serde_json::{json, Map, Value}; use starknet_api::core::ChainId; use tempfile::NamedTempFile; -use test_utils::get_absolute_path; use validator::Validate; #[cfg(feature = "rpc")] diff --git a/crates/papyrus_node/src/main.rs b/crates/papyrus_node/src/main.rs index cb84bfdd03b..ccc07fd4c4c 100644 --- a/crates/papyrus_node/src/main.rs +++ b/crates/papyrus_node/src/main.rs @@ -19,10 +19,11 @@ use papyrus_config::validators::config_validate; use papyrus_config::ConfigError; use papyrus_monitoring_gateway::MonitoringServer; use papyrus_network::network_manager::NetworkError; -use papyrus_network::{network_manager, NetworkConfig, Protocol, Query, ResponseReceivers}; +use papyrus_network::{network_manager, DataType, NetworkConfig, Protocol, ResponseReceivers}; use papyrus_node::config::NodeConfig; use papyrus_node::version::VERSION_FULL; use papyrus_p2p_sync::{P2PSync, P2PSyncConfig, P2PSyncError}; +use papyrus_protobuf::sync::Query; #[cfg(feature = "rpc")] use papyrus_rpc::run_server; use papyrus_storage::{open_storage, update_storage_metrics, StorageReader, StorageWriter}; @@ -223,7 +224,7 @@ async fn run_threads(config: NodeConfig) -> anyhow::Result<()> { p2p_sync_config: P2PSyncConfig, storage_reader: StorageReader, storage_writer: StorageWriter, - query_sender: Sender, + query_sender: Sender<(Query, DataType)>, response_receivers: ResponseReceivers, ) -> Result<(), P2PSyncError> { let sync = P2PSync::new( @@ -239,7 +240,7 @@ async fn run_threads(config: NodeConfig) -> anyhow::Result<()> { type NetworkRunReturn = ( BoxFuture<'static, Result<(), NetworkError>>, - Option<(Sender, ResponseReceivers)>, + Option<(Sender<(Query, DataType)>, ResponseReceivers)>, String, ); @@ -248,8 +249,8 @@ fn run_network(config: Option, storage_reader: StorageReader) -> let mut network_manager = network_manager::NetworkManager::new(network_config.clone(), storage_reader.clone()); let own_peer_id = network_manager.get_own_peer_id(); - let (query_sender, response_receivers) = - network_manager.register_subscriber(vec![Protocol::SignedBlockHeader, Protocol::StateDiff]); + let (query_sender, response_receivers) = network_manager + .register_sqmr_subscriber(vec![Protocol::SignedBlockHeader, Protocol::StateDiff]); (network_manager.run().boxed(), Some((query_sender, response_receivers)), own_peer_id) } diff --git a/crates/papyrus_node/src/main_test.rs b/crates/papyrus_node/src/main_test.rs index 7b4864e2e4f..80a07421e98 100644 --- a/crates/papyrus_node/src/main_test.rs +++ b/crates/papyrus_node/src/main_test.rs @@ -3,8 +3,8 @@ use std::time::Duration; use metrics_exporter_prometheus::PrometheusBuilder; use papyrus_node::config::NodeConfig; use papyrus_storage::{open_storage, StorageConfig}; +use papyrus_test_utils::prometheus_is_contained; use tempfile::TempDir; -use test_utils::prometheus_is_contained; use crate::{run_threads, spawn_storage_metrics_collector}; diff --git a/crates/papyrus_p2p_sync/Cargo.toml b/crates/papyrus_p2p_sync/Cargo.toml index c322aac685e..69b88180cff 100644 --- a/crates/papyrus_p2p_sync/Cargo.toml +++ b/crates/papyrus_p2p_sync/Cargo.toml @@ -14,9 +14,10 @@ papyrus_common = { path = "../papyrus_common", version = "0.4.0-dev.2" } papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } papyrus_network = { path = "../papyrus_network", version = "0.4.0-dev.2" } papyrus_proc_macros = { path = "../papyrus_proc_macros", version = "0.4.0-dev.2" } +papyrus_protobuf = { path = "../papyrus_protobuf", version = "0.4.0-dev.2" } papyrus_storage = { path = "../papyrus_storage", version = "0.4.0-dev.2" } serde.workspace = true -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } thiserror.workspace = true tokio.workspace = true tokio-stream.workspace = true @@ -28,4 +29,4 @@ lazy_static.workspace = true papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } static_assertions.workspace = true rand.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/papyrus_p2p_sync/src/header.rs b/crates/papyrus_p2p_sync/src/header.rs index 1b455a64743..6a1f4752e50 100644 --- a/crates/papyrus_p2p_sync/src/header.rs +++ b/crates/papyrus_p2p_sync/src/header.rs @@ -2,7 +2,8 @@ use std::pin::Pin; use futures::future::BoxFuture; use futures::{FutureExt, Stream, StreamExt}; -use papyrus_network::{DataType, SignedBlockHeader}; +use papyrus_network::DataType; +use papyrus_protobuf::sync::SignedBlockHeader; use papyrus_storage::header::{HeaderStorageReader, HeaderStorageWriter}; use papyrus_storage::{StorageError, StorageReader, StorageWriter}; use starknet_api::block::BlockNumber; diff --git a/crates/papyrus_p2p_sync/src/header_test.rs b/crates/papyrus_p2p_sync/src/header_test.rs index 7f02e6ca033..7782549657f 100644 --- a/crates/papyrus_p2p_sync/src/header_test.rs +++ b/crates/papyrus_p2p_sync/src/header_test.rs @@ -1,6 +1,7 @@ use futures::future::ready; use futures::{SinkExt, StreamExt}; -use papyrus_network::{DataType, Direction, Query, SignedBlockHeader}; +use papyrus_network::DataType; +use papyrus_protobuf::sync::{BlockHashOrNumber, Direction, Query, SignedBlockHeader}; use papyrus_storage::header::HeaderStorageReader; use starknet_api::block::{BlockHeader, BlockNumber}; use tokio::time::timeout; @@ -15,7 +16,7 @@ use crate::test_utils::{ #[tokio::test] async fn signed_headers_basic_flow() { - const NUM_QUERIES: usize = 3; + const NUM_QUERIES: u64 = 3; let (p2p_sync, storage_reader, query_receiver, mut signed_headers_sender, _state_diffs_sender) = setup(); @@ -23,7 +24,7 @@ async fn signed_headers_basic_flow() { create_block_hashes_and_signatures((NUM_QUERIES * HEADER_QUERY_LENGTH).try_into().unwrap()); let mut query_receiver = query_receiver - .filter(|query| ready(matches!(query.data_type, DataType::SignedBlockHeader))); + .filter(|(_query, data_type)| ready(matches!(data_type, DataType::SignedBlockHeader))); // Create a future that will receive queries, send responses and validate the results. let parse_queries_future = async move { @@ -32,23 +33,22 @@ async fn signed_headers_basic_flow() { let end_block_number = (query_index + 1) * HEADER_QUERY_LENGTH; // Receive query and validate it. - let query = query_receiver.next().await.unwrap(); + let (query, _) = query_receiver.next().await.unwrap(); assert_eq!( query, Query { - start_block: BlockNumber(start_block_number.try_into().unwrap()), + start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), direction: Direction::Forward, limit: HEADER_QUERY_LENGTH, step: 1, - data_type: DataType::SignedBlockHeader, } ); for (i, (block_hash, block_signature)) in block_hashes_and_signatures .iter() .enumerate() - .take(end_block_number) - .skip(start_block_number) + .take(end_block_number.try_into().expect("Failed converting u64 to usize")) + .skip(start_block_number.try_into().expect("Failed converting u64 to usize")) { // Send responses signed_headers_sender @@ -95,14 +95,14 @@ async fn signed_headers_basic_flow() { #[tokio::test] async fn sync_sends_new_header_query_if_it_got_partial_responses() { const NUM_ACTUAL_RESPONSES: u8 = 2; - assert!(usize::from(NUM_ACTUAL_RESPONSES) < HEADER_QUERY_LENGTH); + assert!(u64::from(NUM_ACTUAL_RESPONSES) < HEADER_QUERY_LENGTH); let (p2p_sync, _storage_reader, query_receiver, mut signed_headers_sender, _state_diffs_sender) = setup(); let block_hashes_and_signatures = create_block_hashes_and_signatures(NUM_ACTUAL_RESPONSES); let mut query_receiver = query_receiver - .filter(|query| ready(matches!(query.data_type, DataType::SignedBlockHeader))); + .filter(|(_query, data_type)| ready(matches!(data_type, DataType::SignedBlockHeader))); // Create a future that will receive a query, send partial responses and receive the next query. let parse_queries_future = async move { @@ -125,19 +125,19 @@ async fn sync_sends_new_header_query_if_it_got_partial_responses() { signed_headers_sender.send(None).await.unwrap(); // First unwrap is for the timeout. Second unwrap is for the Option returned from Stream. - let query = timeout(TIMEOUT_FOR_NEW_QUERY_AFTER_PARTIAL_RESPONSE, query_receiver.next()) - .await - .unwrap() - .unwrap(); + let (query, _) = + timeout(TIMEOUT_FOR_NEW_QUERY_AFTER_PARTIAL_RESPONSE, query_receiver.next()) + .await + .unwrap() + .unwrap(); assert_eq!( query, Query { - start_block: BlockNumber(NUM_ACTUAL_RESPONSES.into()), + start_block: BlockHashOrNumber::Number(BlockNumber(NUM_ACTUAL_RESPONSES.into())), direction: Direction::Forward, limit: HEADER_QUERY_LENGTH, step: 1, - data_type: DataType::SignedBlockHeader, } ); }; diff --git a/crates/papyrus_p2p_sync/src/lib.rs b/crates/papyrus_p2p_sync/src/lib.rs index 0368edc5c7c..a5dd079d336 100644 --- a/crates/papyrus_p2p_sync/src/lib.rs +++ b/crates/papyrus_p2p_sync/src/lib.rs @@ -15,7 +15,8 @@ use futures::channel::mpsc::{SendError, Sender}; use papyrus_config::converters::deserialize_seconds_to_duration; use papyrus_config::dumping::{ser_optional_param, ser_param, SerializeConfig}; use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; -use papyrus_network::{DataType, Query, ResponseReceivers}; +use papyrus_network::{DataType, ResponseReceivers}; +use papyrus_protobuf::sync::Query; use papyrus_storage::{StorageError, StorageReader, StorageWriter}; use serde::{Deserialize, Serialize}; use starknet_api::block::{BlockNumber, BlockSignature}; @@ -26,15 +27,15 @@ use crate::header::HeaderStreamFactory; use crate::state_diff::StateDiffStreamFactory; use crate::stream_factory::DataStreamFactory; -const STEP: usize = 1; +const STEP: u64 = 1; const ALLOWED_SIGNATURES_LENGTH: usize = 1; const NETWORK_DATA_TIMEOUT: Duration = Duration::from_secs(300); #[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq)] pub struct P2PSyncConfig { - pub num_headers_per_query: usize, - pub num_block_state_diffs_per_query: usize, + pub num_headers_per_query: u64, + pub num_block_state_diffs_per_query: u64, #[serde(deserialize_with = "deserialize_seconds_to_duration")] pub wait_period_for_new_data: Duration, pub stop_sync_at_block_number: Option, @@ -139,7 +140,7 @@ pub struct P2PSync { config: P2PSyncConfig, storage_reader: StorageReader, storage_writer: StorageWriter, - query_sender: Sender, + query_sender: Sender<(Query, DataType)>, response_receivers: ResponseReceivers, } @@ -148,7 +149,7 @@ impl P2PSync { config: P2PSyncConfig, storage_reader: StorageReader, storage_writer: StorageWriter, - query_sender: Sender, + query_sender: Sender<(Query, DataType)>, response_receivers: ResponseReceivers, ) -> Self { Self { config, storage_reader, storage_writer, query_sender, response_receivers } diff --git a/crates/papyrus_p2p_sync/src/state_diff_test.rs b/crates/papyrus_p2p_sync/src/state_diff_test.rs index 3520d69ae8c..ac4cb4ae765 100644 --- a/crates/papyrus_p2p_sync/src/state_diff_test.rs +++ b/crates/papyrus_p2p_sync/src/state_diff_test.rs @@ -4,15 +4,16 @@ use assert_matches::assert_matches; use futures::future::ready; use futures::{FutureExt, SinkExt, StreamExt}; use indexmap::{indexmap, IndexMap}; -use papyrus_network::{DataType, Direction, Query, SignedBlockHeader}; +use papyrus_common::state::create_random_state_diff; +use papyrus_network::DataType; +use papyrus_protobuf::sync::{BlockHashOrNumber, Direction, Query, SignedBlockHeader}; use papyrus_storage::state::StateStorageReader; -use rand::RngCore; +use papyrus_test_utils::get_rng; use starknet_api::block::{BlockHeader, BlockNumber}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::state::{StorageKey, ThinStateDiff}; use static_assertions::const_assert; -use test_utils::get_rng; use crate::test_utils::{ create_block_hashes_and_signatures, @@ -25,34 +26,6 @@ use crate::P2PSyncError; const TIMEOUT_FOR_TEST: Duration = Duration::from_secs(5); -fn create_random_state_diff(rng: &mut impl RngCore) -> ThinStateDiff { - let contract0 = ContractAddress::from(rng.next_u64()); - let contract1 = ContractAddress::from(rng.next_u64()); - let contract2 = ContractAddress::from(rng.next_u64()); - let class_hash = ClassHash(rng.next_u64().into()); - let compiled_class_hash = CompiledClassHash(rng.next_u64().into()); - let deprecated_class_hash = ClassHash(rng.next_u64().into()); - ThinStateDiff { - deployed_contracts: indexmap! { - contract0 => class_hash, contract1 => class_hash, contract2 => deprecated_class_hash - }, - storage_diffs: indexmap! { - contract0 => indexmap! { - 1u64.into() => StarkFelt::ONE, 2u64.into() => StarkFelt::TWO - }, - contract1 => indexmap! { - 3u64.into() => StarkFelt::TWO, 4u64.into() => StarkFelt::ONE - }, - }, - declared_classes: indexmap! { class_hash => compiled_class_hash }, - deprecated_declared_classes: vec![deprecated_class_hash], - nonces: indexmap! { - contract0 => Nonce(StarkFelt::ONE), contract2 => Nonce(StarkFelt::TWO) - }, - replaced_classes: Default::default(), - } -} - fn split_state_diff(state_diff: ThinStateDiff) -> Vec { let mut result = Vec::new(); if !state_diff.deployed_contracts.is_empty() { @@ -111,8 +84,8 @@ async fn state_diff_basic_flow() { // We don't need to read the header query in order to know which headers to send, and we // already validate the header query in a different test. - let mut query_receiver = - query_receiver.filter(|query| ready(matches!(query.data_type, DataType::StateDiff))); + let mut query_receiver = query_receiver + .filter(|(_query, data_type)| ready(matches!(data_type, DataType::StateDiff))); // Create a future that will receive queries, send responses and validate the results. let parse_queries_future = async move { @@ -142,27 +115,21 @@ async fn state_diff_basic_flow() { } for (start_block_number, num_blocks) in [ (0u64, STATE_DIFF_QUERY_LENGTH), - ( - STATE_DIFF_QUERY_LENGTH.try_into().unwrap(), - HEADER_QUERY_LENGTH - STATE_DIFF_QUERY_LENGTH, - ), + (STATE_DIFF_QUERY_LENGTH, HEADER_QUERY_LENGTH - STATE_DIFF_QUERY_LENGTH), ] { // Get a state diff query and validate it - let query = query_receiver.next().await.unwrap(); + let (query, _) = query_receiver.next().await.unwrap(); assert_eq!( query, Query { - start_block: BlockNumber(start_block_number), + start_block: BlockHashOrNumber::Number(BlockNumber(start_block_number)), direction: Direction::Forward, limit: num_blocks, step: 1, - data_type: DataType::StateDiff, } ); - for block_number in - start_block_number..(start_block_number + u64::try_from(num_blocks).unwrap()) - { + for block_number in start_block_number..(start_block_number + num_blocks) { let expected_state_diff: &ThinStateDiff = &state_diffs[usize::try_from(block_number).unwrap()]; let state_diff_parts = split_state_diff(expected_state_diff.clone()); @@ -216,8 +183,8 @@ async fn validate_state_diff_fails( // We don't need to read the header query in order to know which headers to send, and we // already validate the header query in a different test. - let mut query_receiver = - query_receiver.filter(|query| ready(matches!(query.data_type, DataType::StateDiff))); + let mut query_receiver = query_receiver + .filter(|(_query, data_type)| ready(matches!(data_type, DataType::StateDiff))); // Create a future that will receive queries, send responses and validate the results. let parse_queries_future = async move { @@ -236,15 +203,14 @@ async fn validate_state_diff_fails( .unwrap(); // Get a state diff query and validate it - let query = query_receiver.next().await.unwrap(); + let (query, _) = query_receiver.next().await.unwrap(); assert_eq!( query, Query { - start_block: BlockNumber(0), + start_block: BlockHashOrNumber::Number(BlockNumber(0)), direction: Direction::Forward, limit: 1, step: 1, - data_type: DataType::StateDiff, } ); diff --git a/crates/papyrus_p2p_sync/src/stream_factory.rs b/crates/papyrus_p2p_sync/src/stream_factory.rs index 6b50b5d5d3a..0f59df8c50b 100644 --- a/crates/papyrus_p2p_sync/src/stream_factory.rs +++ b/crates/papyrus_p2p_sync/src/stream_factory.rs @@ -7,7 +7,8 @@ use futures::channel::mpsc::Sender; use futures::future::BoxFuture; use futures::stream::BoxStream; use futures::{SinkExt, Stream, StreamExt}; -use papyrus_network::{DataType, Direction, Query}; +use papyrus_network::DataType; +use papyrus_protobuf::sync::{BlockHashOrNumber, Direction, Query}; use papyrus_storage::header::HeaderStorageReader; use papyrus_storage::{StorageError, StorageReader, StorageWriter}; use starknet_api::block::BlockNumber; @@ -47,10 +48,10 @@ pub(crate) trait DataStreamFactory { fn create_stream( mut data_receiver: Pin> + Send>>, - mut query_sender: Sender, + mut query_sender: Sender<(Query, DataType)>, storage_reader: StorageReader, wait_period_for_new_data: Duration, - num_blocks_per_query: usize, + num_blocks_per_query: u64, stop_sync_at_block_number: Option, ) -> BoxStream<'static, Result, P2PSyncError>> { stream! { @@ -61,8 +62,7 @@ pub(crate) trait DataStreamFactory { BlockNumberLimit::HeaderMarker => { let last_block_number = storage_reader.begin_ro_txn()?.get_header_marker()?; let limit = min( - usize::try_from(last_block_number.0 - current_block_number.0) - .expect("failed converting u64 to usize"), + last_block_number.0 - current_block_number.0, num_blocks_per_query, ); if limit == 0 { @@ -73,9 +73,7 @@ pub(crate) trait DataStreamFactory { limit } }; - let end_block_number = current_block_number.0 - + u64::try_from(limit) - .expect("Failed converting usize to u64"); + let end_block_number = current_block_number.0 + limit; debug!( "Downloading {:?} for blocks [{}, {})", Self::DATA_TYPE, @@ -83,13 +81,15 @@ pub(crate) trait DataStreamFactory { end_block_number, ); query_sender - .send(Query { - start_block: current_block_number, - direction: Direction::Forward, - limit, - step: STEP, - data_type: Self::DATA_TYPE, - }) + .send(( + Query { + start_block: BlockHashOrNumber::Number(current_block_number), + direction: Direction::Forward, + limit, + step: STEP, + }, + Self::DATA_TYPE, + )) .await?; while current_block_number.0 < end_block_number { diff --git a/crates/papyrus_p2p_sync/src/test_utils.rs b/crates/papyrus_p2p_sync/src/test_utils.rs index 9580178db1d..a3396effcae 100644 --- a/crates/papyrus_p2p_sync/src/test_utils.rs +++ b/crates/papyrus_p2p_sync/src/test_utils.rs @@ -3,7 +3,8 @@ use std::time::Duration; use futures::channel::mpsc::{Receiver, Sender}; use futures::StreamExt; use lazy_static::lazy_static; -use papyrus_network::{Query, ResponseReceivers, SignedBlockHeader}; +use papyrus_network::{DataType, ResponseReceivers}; +use papyrus_protobuf::sync::{Query, SignedBlockHeader}; use papyrus_storage::test_utils::get_test_storage; use papyrus_storage::StorageReader; use starknet_api::block::{BlockHash, BlockSignature}; @@ -14,8 +15,8 @@ use starknet_api::state::ThinStateDiff; use crate::{P2PSync, P2PSyncConfig}; pub const BUFFER_SIZE: usize = 1000; -pub const HEADER_QUERY_LENGTH: usize = 5; -pub const STATE_DIFF_QUERY_LENGTH: usize = 3; +pub const HEADER_QUERY_LENGTH: u64 = 5; +pub const STATE_DIFF_QUERY_LENGTH: u64 = 3; pub const SLEEP_DURATION_TO_LET_SYNC_ADVANCE: Duration = Duration::from_millis(10); // This should be substantially bigger than SLEEP_DURATION_TO_LET_SYNC_ADVANCE. pub const WAIT_PERIOD_FOR_NEW_DATA: Duration = Duration::from_millis(50); @@ -35,7 +36,7 @@ lazy_static! { pub fn setup() -> ( P2PSync, StorageReader, - Receiver, + Receiver<(Query, DataType)>, Sender>, Sender>, ) { diff --git a/crates/papyrus_proc_macros/Cargo.toml b/crates/papyrus_proc_macros/Cargo.toml index b625328baff..aff953c08d5 100644 --- a/crates/papyrus_proc_macros/Cargo.toml +++ b/crates/papyrus_proc_macros/Cargo.toml @@ -15,7 +15,7 @@ metrics.workspace = true metrics-exporter-prometheus.workspace = true papyrus_common = { path = "../papyrus_common", version = "0.4.0-dev.1" } prometheus-parse.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } [lib] proc-macro = true diff --git a/crates/papyrus_proc_macros/tests/latency_histogram.rs b/crates/papyrus_proc_macros/tests/latency_histogram.rs index 1af5f170787..f4415c2b59f 100644 --- a/crates/papyrus_proc_macros/tests/latency_histogram.rs +++ b/crates/papyrus_proc_macros/tests/latency_histogram.rs @@ -1,8 +1,8 @@ use metrics_exporter_prometheus::PrometheusBuilder; use papyrus_common::metrics::COLLECT_PROFILING_METRICS; use papyrus_proc_macros::latency_histogram; +use papyrus_test_utils::prometheus_is_contained; use prometheus_parse::Value::Untyped; -use test_utils::prometheus_is_contained; #[test] fn latency_histogram_test() { diff --git a/crates/papyrus_protobuf/Cargo.toml b/crates/papyrus_protobuf/Cargo.toml new file mode 100644 index 00000000000..7eea0583ae3 --- /dev/null +++ b/crates/papyrus_protobuf/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "papyrus_protobuf" +version.workspace = true +edition.workspace = true +repository.workspace = true +license-file.workspace = true + +[dependencies] +bytes.workspace = true +indexmap.workspace = true +primitive-types.workspace = true +prost.workspace = true +prost-types.workspace = true +starknet_api = { version = "0.12.0-dev.1" } +thiserror.workspace = true + +[build-dependencies] +prost-build.workspace = true diff --git a/crates/papyrus_protobuf/build.rs b/crates/papyrus_protobuf/build.rs new file mode 100644 index 00000000000..2e8c2eefeb5 --- /dev/null +++ b/crates/papyrus_protobuf/build.rs @@ -0,0 +1,39 @@ +use std::env; +use std::io::{Error, ErrorKind, Result}; +use std::process::Command; + +fn main() -> Result<()> { + println!("Building"); + let protoc = env::var("PROTOC").unwrap_or("protoc".to_string()); + + let protoc_version = String::from_utf8_lossy( + &Command::new(protoc).arg("--version").output().expect("Protoc is not installed.").stdout, + ) + .to_string(); + + let parts: Vec<&str> = protoc_version.split_whitespace().collect(); + let protoc_version_str = parts.get(1).expect("Failed to determine protoc version"); + let mut protoc_version_parts = protoc_version_str + .split('.') + .map(|part| part.parse::().expect("Error parsing protoc version")); + let major = protoc_version_parts.next().expect("Protoc version did not have a major number"); + let minor = protoc_version_parts.next().unwrap_or_default(); + + if major < 3 || (major == 3 && minor < 15) { + Err(Error::new( + ErrorKind::Other, + "protoc version is too old. version 3.15.x or greater is needed.", + )) + } else { + prost_build::compile_protos( + &[ + "src/proto/p2p/proto/header.proto", + "src/proto/p2p/proto/state.proto", + "src/proto/p2p/proto/transaction.proto", + "src/proto/p2p/proto/consensus.proto", + ], + &["src/proto/"], + )?; + Ok(()) + } +} diff --git a/crates/papyrus_protobuf/src/consensus.rs b/crates/papyrus_protobuf/src/consensus.rs new file mode 100644 index 00000000000..0edb5230e99 --- /dev/null +++ b/crates/papyrus_protobuf/src/consensus.rs @@ -0,0 +1,10 @@ +use starknet_api::block::BlockHash; +use starknet_api::core::ContractAddress; +use starknet_api::transaction::Transaction; + +pub struct Proposal { + pub height: u64, + pub proposer: ContractAddress, + pub transactions: Vec, + pub block_hash: BlockHash, +} diff --git a/crates/papyrus_network/src/converters/protobuf_conversion/common.rs b/crates/papyrus_protobuf/src/converters/common.rs similarity index 73% rename from crates/papyrus_network/src/converters/protobuf_conversion/common.rs rename to crates/papyrus_protobuf/src/converters/common.rs index e89ea0fee1f..bdb0bf2bd29 100644 --- a/crates/papyrus_network/src/converters/protobuf_conversion/common.rs +++ b/crates/papyrus_protobuf/src/converters/common.rs @@ -1,9 +1,9 @@ use starknet_api::block::{BlockHash, BlockNumber}; -use starknet_api::data_availability::L1DataAvailabilityMode; +use starknet_api::data_availability::{DataAvailabilityMode, L1DataAvailabilityMode}; use super::ProtobufConversionError; -use crate::protobuf_messages::protobuf::{self}; -use crate::{BlockHashOrNumber, Direction, InternalQuery, Query}; +use crate::protobuf; +use crate::sync::{BlockHashOrNumber, Direction, Query}; #[cfg(test)] #[allow(dead_code)] @@ -156,13 +156,6 @@ impl TestInstance for protobuf::Address { #[cfg(test)] impl TestInstance for protobuf::Patricia { - fn test_instance() -> Self { - Self { height: PATRICIA_HEIGHT, root: Some(protobuf::Hash::test_instance()) } - } -} - -#[cfg(test)] -impl TestInstance for protobuf::Merkle { fn test_instance() -> Self { Self { n_leaves: 0, root: Some(protobuf::Hash::test_instance()) } } @@ -185,7 +178,29 @@ impl TestInstance for protobuf::ConsensusSignature { } } -impl TryFrom for InternalQuery { +#[allow(dead_code)] +pub(super) fn enum_int_to_volition_domain( + value: i32, +) -> Result { + match value { + 0 => Ok(DataAvailabilityMode::L1), + 1 => Ok(DataAvailabilityMode::L2), + _ => Err(ProtobufConversionError::OutOfRangeValue { + type_description: "VolitionDomain", + value_as_str: format!("{value}"), + }), + } +} + +// TODO(shahak): Internalize this once network doesn't depend on protobuf. +pub fn volition_domain_to_enum_int(value: DataAvailabilityMode) -> i32 { + match value { + DataAvailabilityMode::L1 => 0, + DataAvailabilityMode::L2 => 1, + } +} + +impl TryFrom for Query { type Error = ProtobufConversionError; fn try_from(value: protobuf::Iteration) -> Result { @@ -212,21 +227,63 @@ impl TryFrom for InternalQuery { }; let limit = value.limit; let step = value.step; - Ok(InternalQuery { start_block, direction, limit, step }) + Ok(Query { start_block, direction, limit, step }) } } impl From for protobuf::Iteration { fn from(value: Query) -> Self { - let start = protobuf::iteration::Start::BlockNumber(value.start_block.0); + let start = match value.start_block { + BlockHashOrNumber::Number(BlockNumber(number)) => { + protobuf::iteration::Start::BlockNumber(number) + } + BlockHashOrNumber::Hash(block_hash) => { + protobuf::iteration::Start::Header(block_hash.into()) + } + }; Self { start: Some(start), direction: match value.direction { Direction::Forward => 0, Direction::Backward => 1, }, - limit: value.limit as u64, - step: value.step as u64, + limit: value.limit, + step: value.step, } } } + +// TODO: use the conversion in Starknet api once its upgraded +pub(super) fn try_from_starkfelt_to_u128( + felt: starknet_api::hash::StarkFelt, +) -> Result { + const COMPLIMENT_OF_U128: usize = 16; // 32 - 16 + let (rest, u128_bytes) = felt.bytes().split_at(COMPLIMENT_OF_U128); + if rest != [0u8; COMPLIMENT_OF_U128] { + return Err("Value out of range"); + } + + let bytes: [u8; 16] = match u128_bytes.try_into() { + Ok(b) => b, + Err(_) => return Err("Failed to convert bytes to u128"), + }; + + Ok(u128::from_be_bytes(bytes)) +} +// TODO: use the conversion in Starknet api once its upgraded +pub(super) fn try_from_starkfelt_to_u32( + felt: starknet_api::hash::StarkFelt, +) -> Result { + const COMPLIMENT_OF_U32: usize = 28; // 32 - 4 + let (rest, u32_bytes) = felt.bytes().split_at(COMPLIMENT_OF_U32); + if rest != [0u8; COMPLIMENT_OF_U32] { + return Err("Value out of range"); + } + + let bytes: [u8; 4] = match u32_bytes.try_into() { + Ok(b) => b, + Err(_) => return Err("Failed to convert bytes to u32"), + }; + + Ok(u32::from_be_bytes(bytes)) +} diff --git a/crates/papyrus_protobuf/src/converters/consensus.rs b/crates/papyrus_protobuf/src/converters/consensus.rs new file mode 100644 index 00000000000..93982129a1d --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/consensus.rs @@ -0,0 +1,34 @@ +use std::convert::{TryFrom, TryInto}; + +use starknet_api::block::BlockHash; +use starknet_api::hash::StarkHash; +use starknet_api::transaction::Transaction; + +use crate::consensus::Proposal; +use crate::converters::ProtobufConversionError; +use crate::protobuf; + +impl TryFrom for Proposal { + type Error = ProtobufConversionError; + + fn try_from(value: protobuf::Proposal) -> Result { + let transactions = value + .transactions + .into_iter() + .map(|tx| tx.try_into()) + .collect::, ProtobufConversionError>>()?; + + let height = value.height; + let contract_address = value + .proposer + .ok_or(ProtobufConversionError::MissingField { field_description: "proposer" })? + .try_into()?; + let block_hash: StarkHash = value + .block_hash + .ok_or(ProtobufConversionError::MissingField { field_description: "block_hash" })? + .try_into()?; + let block_hash = BlockHash(block_hash); + + Ok(Proposal { height, proposer: contract_address, transactions, block_hash }) + } +} diff --git a/crates/papyrus_network/src/converters/protobuf_conversion/header.rs b/crates/papyrus_protobuf/src/converters/header.rs similarity index 74% rename from crates/papyrus_network/src/converters/protobuf_conversion/header.rs rename to crates/papyrus_protobuf/src/converters/header.rs index 0a583580364..f495c1f244e 100644 --- a/crates/papyrus_network/src/converters/protobuf_conversion/header.rs +++ b/crates/papyrus_protobuf/src/converters/header.rs @@ -1,3 +1,8 @@ +#[cfg(test)] +#[path = "header_test.rs"] +mod header_test; + +use prost::Message; use starknet_api::block::{ BlockHash, BlockHeader, @@ -16,10 +21,16 @@ use starknet_api::core::{ use starknet_api::crypto::Signature; use super::common::{enum_int_to_l1_data_availability_mode, l1_data_availability_mode_to_enum_int}; -use super::{ProtobufConversionError, ProtobufResponseToDataError}; -use crate::db_executor::Data; -use crate::protobuf_messages::protobuf::{self}; -use crate::{DataType, InternalQuery, Query, SignedBlockHeader}; +use super::ProtobufConversionError; +use crate::sync::{DataOrFin, HeaderQuery, Query, SignedBlockHeader}; +use crate::{auto_impl_into_and_try_from_vec_u8, protobuf}; + +impl TryFrom for DataOrFin { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::BlockHeadersResponse) -> Result { + Ok(Self(value.try_into()?)) + } +} impl TryFrom for Option { type Error = ProtobufConversionError; @@ -66,10 +77,9 @@ impl TryFrom for SignedBlockHeader { .map(SequencerContractAddress)?; let state_root = value - .state - .and_then(|state| state.root) + .state_root .ok_or(ProtobufConversionError::MissingField { - field_description: "SignedBlockHeader::state", + field_description: "SignedBlockHeader::state_root", })? .try_into() .map(GlobalRoot)?; @@ -81,7 +91,7 @@ impl TryFrom for SignedBlockHeader { let transaction_commitment = value .transactions .map(|transactions| { - Ok(TransactionCommitment( + Ok::<_, ProtobufConversionError>(TransactionCommitment( transactions .root .ok_or(ProtobufConversionError::MissingField { @@ -100,7 +110,7 @@ impl TryFrom for SignedBlockHeader { let event_commitment = value .events .map(|events| { - Ok(EventCommitment( + Ok::<_, ProtobufConversionError>(EventCommitment( events .root .ok_or(ProtobufConversionError::MissingField { @@ -111,6 +121,13 @@ impl TryFrom for SignedBlockHeader { }) .transpose()?; + let state_diff_length = value.state_diff_commitment.as_ref().map(|state_diff_commitment| { + state_diff_commitment + .state_diff_length + .try_into() + .expect("Failed converting u64 to usize") + }); + let l1_da_mode = enum_int_to_l1_data_availability_mode(value.l1_data_availability_mode)?; let starknet_version = StarknetVersion(value.protocol_version); @@ -153,9 +170,6 @@ impl TryFrom for SignedBlockHeader { ), }; - let state_diff_length = - Some(value.state_diff_length.try_into().expect("Failed converting u64 to usize")); - Ok(SignedBlockHeader { block_header: BlockHeader { block_hash, @@ -169,13 +183,13 @@ impl TryFrom for SignedBlockHeader { l1_da_mode, // TODO(shahak): fill this. state_diff_commitment: None, + state_diff_length, transaction_commitment, event_commitment, - state_diff_length, - // TODO(shahak): fill this. - receipt_commitment: None, n_transactions, n_events, + // TODO(shahak): fill this. + receipt_commitment: None, starknet_version, }, // collect will convert from Vec to Result. @@ -188,6 +202,12 @@ impl TryFrom for SignedBlockHeader { } } +impl From> for protobuf::BlockHeadersResponse { + fn from(value: DataOrFin) -> Self { + value.0.into() + } +} + impl From<(BlockHeader, Vec)> for protobuf::SignedBlockHeader { fn from((header, signatures): (BlockHeader, Vec)) -> Self { Self { @@ -196,30 +216,28 @@ impl From<(BlockHeader, Vec)> for protobuf::SignedBlockHeader { number: header.block_number.0, time: header.timestamp.0, sequencer_address: Some(header.sequencer.0.into()), - // TODO(shahak): fill this. If the state_diff_length is None make this None. - state_diff_commitment: None, - state_diff_length: header - .state_diff_length - // If state_diff_length is None, then state_diff_commitment is also None and the - // other peer will know that this node doesn't know about the state diff. - .unwrap_or_default() - .try_into() - .expect("Failed converting u64 to usize"), - state: Some(protobuf::Patricia { - // TODO(shahak): fill this. - height: 0, - root: Some(header.state_root.0.into()), + state_diff_commitment: Some(protobuf::StateDiffCommitment { + state_diff_length: header + .state_diff_length + // If state_diff_length is None, then state_diff_commitment is also None and the + // other peer will know that this node doesn't know about the state diff. + .unwrap_or(0) + .try_into() + .expect("Converting usize to u64 failed"), + // TODO: fill this. + root: None, }), + state_root: Some(header.state_root.0.into()), // This will be Some only if both n_transactions and transaction_commitment are Some. transactions: header.n_transactions.and_then(|n_transactions| { - header.transaction_commitment.map(|transaction_commitment| protobuf::Merkle { + header.transaction_commitment.map(|transaction_commitment| protobuf::Patricia { n_leaves: n_transactions.try_into().expect("Converting usize to u64 failed"), root: Some(transaction_commitment.0.into()), }) }), // This will be Some only if both n_events and event_commitment are Some. events: header.n_events.and_then(|n_events| { - header.event_commitment.map(|event_commitment| protobuf::Merkle { + header.event_commitment.map(|event_commitment| protobuf::Patricia { n_leaves: n_events.try_into().expect("Converting usize to u64 failed"), root: Some(event_commitment.0.into()), }) @@ -263,71 +281,60 @@ impl From for protobuf::ConsensusSignature } } -impl TryFrom for protobuf::BlockHeadersResponse { - type Error = ProtobufResponseToDataError; - - fn try_from(data: Data) -> Result { +impl From> for protobuf::BlockHeadersResponse { + fn from(data: Option) -> Self { match data { - Data::BlockHeaderAndSignature { header, signatures } => { - Ok(protobuf::BlockHeadersResponse { + Some(SignedBlockHeader { block_header, signatures }) => { + protobuf::BlockHeadersResponse { header_message: Some(protobuf::block_headers_response::HeaderMessage::Header( - (header, signatures).into(), + (block_header, signatures).into(), )), - }) + } } - Data::Fin(data_type) => match data_type { - DataType::SignedBlockHeader => Ok(protobuf::BlockHeadersResponse { - header_message: Some(protobuf::block_headers_response::HeaderMessage::Fin( - protobuf::Fin {}, - )), - }), - _ => Err(ProtobufResponseToDataError::UnsupportedDataType { - data_type: data_type.to_string(), - type_description: "BlockHeadersResponse".to_string(), - }), + None => protobuf::BlockHeadersResponse { + header_message: Some(protobuf::block_headers_response::HeaderMessage::Fin( + protobuf::Fin {}, + )), }, - Data::StateDiff { .. } => Err(ProtobufResponseToDataError::UnsupportedDataType { - data_type: "StateDiff".to_string(), - type_description: "BlockHeadersResponse".to_string(), - }), } } } -impl TryFrom for Data { - type Error = ProtobufConversionError; +auto_impl_into_and_try_from_vec_u8!(DataOrFin, protobuf::BlockHeadersResponse); - fn try_from(value: protobuf::BlockHeadersResponse) -> Result { - match value.header_message { - Some(protobuf::block_headers_response::HeaderMessage::Header(header)) => { - let signed_block_header = SignedBlockHeader::try_from(header)?; - Ok(Data::BlockHeaderAndSignature { - header: signed_block_header.block_header, - signatures: signed_block_header.signatures, - }) - } - Some(protobuf::block_headers_response::HeaderMessage::Fin(_)) => { - Ok(Data::Fin(DataType::SignedBlockHeader)) - } - None => Err(ProtobufConversionError::MissingField { - field_description: "BlockHeadersResponse::header_message", - }), - } +// TODO(shahak): Erase this once network stops using it. +impl TryFrom for Query { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::BlockHeadersRequest) -> Result { + Ok(HeaderQuery::try_from(value)?.0) } } -impl TryFrom for InternalQuery { +impl TryFrom for HeaderQuery { type Error = ProtobufConversionError; fn try_from(value: protobuf::BlockHeadersRequest) -> Result { - let value = value.iteration.ok_or(ProtobufConversionError::MissingField { - field_description: "BlockHeadersRequest::iteration", - })?; - value.try_into() + Ok(HeaderQuery( + value + .iteration + .ok_or(ProtobufConversionError::MissingField { + field_description: "BlockHeadersRequest::iteration", + })? + .try_into()?, + )) } } +// TODO(shahak): Erase this once network stops using it. impl From for protobuf::BlockHeadersRequest { fn from(value: Query) -> Self { protobuf::BlockHeadersRequest { iteration: Some(value.into()) } } } + +impl From for protobuf::BlockHeadersRequest { + fn from(value: HeaderQuery) -> Self { + protobuf::BlockHeadersRequest { iteration: Some(value.0.into()) } + } +} + +auto_impl_into_and_try_from_vec_u8!(HeaderQuery, protobuf::BlockHeadersRequest); diff --git a/crates/papyrus_protobuf/src/converters/header_test.rs b/crates/papyrus_protobuf/src/converters/header_test.rs new file mode 100644 index 00000000000..ade49177aac --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/header_test.rs @@ -0,0 +1,40 @@ +use starknet_api::block::{BlockHeader, BlockNumber}; + +use crate::sync::{BlockHashOrNumber, DataOrFin, Direction, HeaderQuery, Query, SignedBlockHeader}; + +#[test] +fn block_header_to_bytes_and_back() { + let data = DataOrFin(Some(SignedBlockHeader { + // TODO(shahak): Remove state_diff_length from here once we correctly deduce if it should + // be None or Some. + block_header: BlockHeader { state_diff_length: Some(0), ..Default::default() }, + signatures: vec![], + })); + dbg!(&data); + let bytes_data = Vec::::from(data.clone()); + + let res_data = DataOrFin::try_from(bytes_data).unwrap(); + assert_eq!(res_data, data); +} + +#[test] +fn fin_to_bytes_and_back() { + let bytes_data = Vec::::from(DataOrFin::(None)); + + let res_data = DataOrFin::::try_from(bytes_data).unwrap(); + assert!(res_data.0.is_none()); +} + +#[test] +fn header_query_to_bytes_and_back() { + let query = HeaderQuery(Query { + start_block: BlockHashOrNumber::Number(BlockNumber(0)), + direction: Direction::Forward, + limit: 1, + step: 1, + }); + + let bytes = Vec::::from(query.clone()); + let res_query = HeaderQuery::try_from(bytes).unwrap(); + assert_eq!(query, res_query); +} diff --git a/crates/papyrus_protobuf/src/converters/mod.rs b/crates/papyrus_protobuf/src/converters/mod.rs new file mode 100644 index 00000000000..a71dbc99132 --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/mod.rs @@ -0,0 +1,49 @@ +// TODO(shahak): Internalize this once network doesn't depend on protobuf. +pub mod common; +pub mod consensus; +mod header; +mod receipt; +// TODO(shahak): Internalize this once network doesn't depend on protobuf. +pub mod state_diff; +mod transaction; + +use prost::DecodeError; + +#[derive(thiserror::Error, Debug)] +pub enum ProtobufConversionError { + #[error("Type `{type_description}` got out of range value {value_as_str}")] + OutOfRangeValue { type_description: &'static str, value_as_str: String }, + #[error("Missing field `{field_description}`")] + MissingField { field_description: &'static str }, + #[error("Type `{type_description}` should be {num_expected} bytes but it got {value:?}.")] + BytesDataLengthMismatch { type_description: &'static str, num_expected: usize, value: Vec }, + #[error(transparent)] + DecodeError(#[from] DecodeError), +} + +#[macro_export] +macro_rules! auto_impl_into_and_try_from_vec_u8 { + ($T:ty, $ProtobufT:ty) => { + impl From<$T> for Vec { + fn from(value: $T) -> Self { + let protobuf_value = <$ProtobufT>::from(value); + protobuf_value.encode_to_vec() + } + } + $crate::auto_impl_try_from_vec_u8!($T, $ProtobufT); + }; +} + +// TODO(shahak): Remove this macro once all types implement both directions. +#[macro_export] +macro_rules! auto_impl_try_from_vec_u8 { + ($T:ty, $ProtobufT:ty) => { + impl TryFrom> for $T { + type Error = ProtobufConversionError; + fn try_from(value: Vec) -> Result { + let protobuf_value = <$ProtobufT>::decode(&value[..])?; + <$T>::try_from(protobuf_value) + } + } + }; +} diff --git a/crates/papyrus_protobuf/src/converters/receipt.rs b/crates/papyrus_protobuf/src/converters/receipt.rs new file mode 100644 index 00000000000..c71c9614f81 --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/receipt.rs @@ -0,0 +1,479 @@ +use std::collections::HashMap; + +use starknet_api::core::{ContractAddress, EthAddress, PatriciaKey}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::{ + Builtin, + DeclareTransactionOutput, + DeployAccountTransactionOutput, + DeployTransactionOutput, + ExecutionResources, + Fee, + InvokeTransactionOutput, + L1HandlerTransactionOutput, + L2ToL1Payload, + MessageToL1, + RevertedTransactionExecutionStatus, + TransactionExecutionStatus, + TransactionOutput, +}; + +use super::common::try_from_starkfelt_to_u128; +use super::ProtobufConversionError; +use crate::protobuf; + +impl TryFrom for TransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::Receipt) -> Result { + let receipt = value + .r#type + .ok_or(ProtobufConversionError::MissingField { field_description: "Receipt::type" })?; + match receipt { + protobuf::receipt::Type::Invoke(invoke) => { + Ok(TransactionOutput::Invoke(InvokeTransactionOutput::try_from(invoke)?)) + } + protobuf::receipt::Type::L1Handler(l1_handler) => { + Ok(TransactionOutput::L1Handler(L1HandlerTransactionOutput::try_from(l1_handler)?)) + } + protobuf::receipt::Type::Declare(declare) => { + Ok(TransactionOutput::Declare(DeclareTransactionOutput::try_from(declare)?)) + } + protobuf::receipt::Type::DeprecatedDeploy(deploy) => { + Ok(TransactionOutput::Deploy(DeployTransactionOutput::try_from(deploy)?)) + } + protobuf::receipt::Type::DeployAccount(deploy_account) => { + Ok(TransactionOutput::DeployAccount(DeployAccountTransactionOutput::try_from( + deploy_account, + )?)) + } + } + } +} + +impl From for protobuf::Receipt { + fn from(value: TransactionOutput) -> Self { + match value { + TransactionOutput::Invoke(invoke) => { + protobuf::Receipt { r#type: Some(protobuf::receipt::Type::Invoke(invoke.into())) } + } + TransactionOutput::L1Handler(l1_handler) => protobuf::Receipt { + r#type: Some(protobuf::receipt::Type::L1Handler(l1_handler.into())), + }, + TransactionOutput::Declare(declare) => { + protobuf::Receipt { r#type: Some(protobuf::receipt::Type::Declare(declare.into())) } + } + TransactionOutput::Deploy(deploy) => protobuf::Receipt { + r#type: Some(protobuf::receipt::Type::DeprecatedDeploy(deploy.into())), + }, + TransactionOutput::DeployAccount(deploy_account) => protobuf::Receipt { + r#type: Some(protobuf::receipt::Type::DeployAccount(deploy_account.into())), + }, + } + } +} + +// The output will have an empty events vec +impl TryFrom for DeployAccountTransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::DeployAccount) -> Result { + let (actual_fee, messages_sent, execution_status, execution_resources) = + parse_common_receipt_fields(value.common)?; + + let events = vec![]; + + let contract_address = + value.contract_address.ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccount::contract_address", + })?; + let felt = StarkFelt::try_from(contract_address)?; + let contract_address = ContractAddress(PatriciaKey::try_from(felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "PatriciaKey", + value_as_str: format!("{felt:?}"), + } + })?); + + Ok(Self { + actual_fee, + messages_sent, + events, + contract_address, + execution_status, + execution_resources, + }) + } +} + +impl From for protobuf::receipt::DeployAccount { + /// The returned price_unit isn't correct. + /// It can be fixed by calling set_price_unit_based_on_transaction + fn from(value: DeployAccountTransactionOutput) -> Self { + let common = create_proto_receipt_common_from_txn_output_fields( + value.actual_fee, + value.messages_sent, + value.execution_resources, + value.execution_status, + ); + + protobuf::receipt::DeployAccount { + common: Some(common), + contract_address: Some(StarkFelt::from(value.contract_address).into()), + } + } +} + +// The output will have an empty events vec +impl TryFrom for DeployTransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::Deploy) -> Result { + let (actual_fee, messages_sent, execution_status, execution_resources) = + parse_common_receipt_fields(value.common)?; + + let events = vec![]; + + let contract_address = + value.contract_address.ok_or(ProtobufConversionError::MissingField { + field_description: "Deploy::contract_address", + })?; + let felt = StarkFelt::try_from(contract_address)?; + let contract_address = ContractAddress(PatriciaKey::try_from(felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "PatriciaKey", + value_as_str: format!("{felt:?}"), + } + })?); + + Ok(Self { + actual_fee, + messages_sent, + events, + contract_address, + execution_status, + execution_resources, + }) + } +} + +impl From for protobuf::receipt::Deploy { + /// The returned price_unit isn't correct. + /// It can be fixed by calling set_price_unit_based_on_transaction + fn from(value: DeployTransactionOutput) -> Self { + let common = create_proto_receipt_common_from_txn_output_fields( + value.actual_fee, + value.messages_sent, + value.execution_resources, + value.execution_status, + ); + + protobuf::receipt::Deploy { + common: Some(common), + contract_address: Some(StarkFelt::from(value.contract_address).into()), + } + } +} + +// The output will have an empty events vec +impl TryFrom for DeclareTransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::Declare) -> Result { + let (actual_fee, messages_sent, execution_status, execution_resources) = + parse_common_receipt_fields(value.common)?; + + let events = vec![]; + + Ok(Self { actual_fee, messages_sent, events, execution_status, execution_resources }) + } +} + +impl From for protobuf::receipt::Declare { + /// The returned price_unit isn't correct. + /// It can be fixed by calling set_price_unit_based_on_transaction + fn from(value: DeclareTransactionOutput) -> Self { + let common = create_proto_receipt_common_from_txn_output_fields( + value.actual_fee, + value.messages_sent, + value.execution_resources, + value.execution_status, + ); + + protobuf::receipt::Declare { common: Some(common) } + } +} + +// The output will have an empty events vec +impl TryFrom for InvokeTransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::Invoke) -> Result { + let (actual_fee, messages_sent, execution_status, execution_resources) = + parse_common_receipt_fields(value.common)?; + + let events = vec![]; + + Ok(Self { actual_fee, messages_sent, events, execution_status, execution_resources }) + } +} + +impl From for protobuf::receipt::Invoke { + /// The returned price_unit isn't correct. + /// It can be fixed by calling set_price_unit_based_on_transaction + fn from(value: InvokeTransactionOutput) -> Self { + let common = create_proto_receipt_common_from_txn_output_fields( + value.actual_fee, + value.messages_sent, + value.execution_resources, + value.execution_status, + ); + + protobuf::receipt::Invoke { common: Some(common) } + } +} + +// The output will have an empty events vec +impl TryFrom for L1HandlerTransactionOutput { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::L1Handler) -> Result { + let (actual_fee, messages_sent, execution_status, execution_resources) = + parse_common_receipt_fields(value.common)?; + + let events = vec![]; + + Ok(Self { actual_fee, messages_sent, events, execution_status, execution_resources }) + } +} + +impl From for protobuf::receipt::L1Handler { + /// The returned price_unit isn't correct. + /// It can be fixed by calling set_price_unit_based_on_transaction + fn from(value: L1HandlerTransactionOutput) -> Self { + let common = create_proto_receipt_common_from_txn_output_fields( + value.actual_fee, + value.messages_sent, + value.execution_resources, + value.execution_status, + ); + + protobuf::receipt::L1Handler { common: Some(common), msg_hash: None } + } +} + +type ProtobufBuiltinCounter = protobuf::receipt::execution_resources::BuiltinCounter; + +impl TryFrom for HashMap { + type Error = ProtobufConversionError; + fn try_from(value: ProtobufBuiltinCounter) -> Result { + let mut builtin_instance_counter = HashMap::new(); + builtin_instance_counter.insert(Builtin::RangeCheck, u64::from(value.range_check)); + builtin_instance_counter.insert(Builtin::Pedersen, u64::from(value.pedersen)); + builtin_instance_counter.insert(Builtin::Poseidon, u64::from(value.poseidon)); + builtin_instance_counter.insert(Builtin::EcOp, u64::from(value.ec_op)); + builtin_instance_counter.insert(Builtin::Ecdsa, u64::from(value.ecdsa)); + builtin_instance_counter.insert(Builtin::Bitwise, u64::from(value.bitwise)); + builtin_instance_counter.insert(Builtin::Keccak, u64::from(value.keccak)); + builtin_instance_counter.insert(Builtin::SegmentArena, 0); + Ok(builtin_instance_counter) + } +} + +impl From> for ProtobufBuiltinCounter { + fn from(value: HashMap) -> Self { + let builtin_counter = ProtobufBuiltinCounter { + range_check: u32::try_from(*value.get(&Builtin::RangeCheck).unwrap_or(&0)) + // TODO: should not panic + .expect("Failed to convert u64 to u32"), + pedersen: u32::try_from(*value.get(&Builtin::Pedersen).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + poseidon: u32::try_from(*value.get(&Builtin::Poseidon).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + ec_op: u32::try_from(*value.get(&Builtin::EcOp).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + ecdsa: u32::try_from(*value.get(&Builtin::Ecdsa).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + bitwise: u32::try_from(*value.get(&Builtin::Bitwise).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + keccak: u32::try_from(*value.get(&Builtin::Keccak).unwrap_or(&0)) + .expect("Failed to convert u64 to u32"), + output: 0, + }; + builtin_counter + } +} + +impl TryFrom for ExecutionResources { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::receipt::ExecutionResources) -> Result { + let builtin_instance_counter = value + .builtins + .ok_or(ProtobufConversionError::MissingField { field_description: "builtins" })?; + let builtin_instance_counter = HashMap::::try_from(builtin_instance_counter)?; + + // TODO: remove all non-da gas consumed + let da_l1_gas_consumed_felt = + StarkFelt::try_from(value.l1_gas.ok_or(ProtobufConversionError::MissingField { + field_description: "ExecutionResources::l1_gas", + })?)?; + let da_l1_gas_consumed = da_l1_gas_consumed_felt.try_into().map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u64", + value_as_str: format!("{da_l1_gas_consumed_felt:?}"), + } + })?; + + let da_l1_data_gas_consumed_felt = StarkFelt::try_from(value.l1_data_gas.ok_or( + ProtobufConversionError::MissingField { + field_description: "ExecutionResources::l1_data_gas", + }, + )?)?; + let da_l1_data_gas_consumed = da_l1_data_gas_consumed_felt.try_into().map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u64", + value_as_str: format!("{da_l1_data_gas_consumed_felt:?}"), + } + })?; + + let execution_resources = ExecutionResources { + steps: u64::from(value.steps), + builtin_instance_counter, + memory_holes: u64::from(value.memory_holes), + da_l1_gas_consumed, + da_l1_data_gas_consumed, + }; + Ok(execution_resources) + } +} + +impl From for protobuf::receipt::ExecutionResources { + fn from(value: ExecutionResources) -> Self { + let builtin_instance_counter = ProtobufBuiltinCounter::from(value.builtin_instance_counter); + // TODO: add all l1 gas consumed, not just da + let l1_gas = StarkFelt::from(value.da_l1_gas_consumed).into(); + let l1_data_gas = StarkFelt::from(value.da_l1_data_gas_consumed).into(); + // TODO: should not panic + let steps = u32::try_from(value.steps).expect("Failed to convert u64 to u32"); + let memory_holes = u32::try_from(value.memory_holes).expect("Failed to convert u64 to u32"); + + protobuf::receipt::ExecutionResources { + builtins: Some(builtin_instance_counter), + steps, + memory_holes, + l1_gas: Some(l1_gas), + l1_data_gas: Some(l1_data_gas), + } + } +} + +impl TryFrom for EthAddress { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::EthereumAddress) -> Result { + let mut felt = [0; 20]; + if value.elements.len() != 20 { + return Err(ProtobufConversionError::BytesDataLengthMismatch { + type_description: "EthereumAddress", + num_expected: 20, + value: value.elements, + }); + } + felt.copy_from_slice(&value.elements); + Ok(EthAddress(primitive_types::H160(felt))) + } +} +impl From for protobuf::EthereumAddress { + fn from(value: EthAddress) -> Self { + let elements = value.0.as_bytes().to_vec(); + protobuf::EthereumAddress { elements } + } +} + +impl TryFrom for MessageToL1 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::MessageToL1) -> Result { + let from_address_felt = StarkFelt::try_from(value.from_address.ok_or( + ProtobufConversionError::MissingField { + field_description: "MessageToL1::from_address", + }, + )?)?; + let from_address = ContractAddress::try_from(from_address_felt) + .expect("Converting ContractAddress from StarkFelt failed"); + + let to_address = EthAddress::try_from(value.to_address.ok_or( + ProtobufConversionError::MissingField { field_description: "MessageToL1::to_address" }, + )?)?; + + let payload = L2ToL1Payload( + value.payload.into_iter().map(StarkFelt::try_from).collect::, _>>()?, + ); + + Ok(MessageToL1 { from_address, to_address, payload }) + } +} + +impl From for protobuf::MessageToL1 { + fn from(value: MessageToL1) -> Self { + let from_address = StarkFelt::from(value.from_address).into(); + let to_address = value.to_address.into(); + let payload = value.payload.0.into_iter().map(protobuf::Felt252::from).collect(); + protobuf::MessageToL1 { + from_address: Some(from_address), + to_address: Some(to_address), + payload, + } + } +} + +fn parse_common_receipt_fields( + common: Option, +) -> Result< + (Fee, Vec, TransactionExecutionStatus, ExecutionResources), + ProtobufConversionError, +> { + let common = + common.ok_or(ProtobufConversionError::MissingField { field_description: "Common" })?; + let actual_fee_felt = + StarkFelt::try_from(common.actual_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "Common::actual_fee", + })?)?; + let actual_fee = Fee(try_from_starkfelt_to_u128(actual_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{actual_fee_felt:?}"), + } + })?); + let messages_sent = common + .messages_sent + .into_iter() + .map(MessageToL1::try_from) + .collect::, _>>()?; + let execution_status = + common.revert_reason.map_or(TransactionExecutionStatus::Succeeded, |revert_reason| { + TransactionExecutionStatus::Reverted(RevertedTransactionExecutionStatus { + revert_reason, + }) + }); + let execution_resources = ExecutionResources::try_from(common.execution_resources.ok_or( + ProtobufConversionError::MissingField { field_description: "Common::execution_resources" }, + )?)?; + Ok((actual_fee, messages_sent, execution_status, execution_resources)) +} + +fn create_proto_receipt_common_from_txn_output_fields( + actual_fee: Fee, + messages_sent: Vec, + execution_resources: ExecutionResources, + execution_status: TransactionExecutionStatus, +) -> protobuf::receipt::Common { + let actual_fee = StarkFelt::from(actual_fee).into(); + let messages_sent = messages_sent.into_iter().map(protobuf::MessageToL1::from).collect(); + let execution_resources = execution_resources.into(); + let revert_reason = + if let TransactionExecutionStatus::Reverted(reverted_status) = execution_status { + Some(reverted_status.revert_reason) + } else { + None + }; + protobuf::receipt::Common { + actual_fee: Some(actual_fee), + price_unit: 0, + messages_sent, + execution_resources: Some(execution_resources), + revert_reason, + } +} diff --git a/crates/papyrus_protobuf/src/converters/state_diff.rs b/crates/papyrus_protobuf/src/converters/state_diff.rs new file mode 100644 index 00000000000..a6c3dcba222 --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/state_diff.rs @@ -0,0 +1,345 @@ +use indexmap::IndexMap; +use prost::Message; +use starknet_api::core::{ClassHash, CompiledClassHash, Nonce}; +use starknet_api::data_availability::DataAvailabilityMode; +use starknet_api::hash::StarkFelt; +use starknet_api::state::{StorageKey, ThinStateDiff}; + +use super::common::volition_domain_to_enum_int; +use super::ProtobufConversionError; +use crate::sync::{ + ContractDiff, + DataOrFin, + DeclaredClass, + DeprecatedDeclaredClass, + Query, + StateDiffChunk, + StateDiffQuery, +}; +use crate::{auto_impl_into_and_try_from_vec_u8, auto_impl_try_from_vec_u8, protobuf}; + +pub const DOMAIN: DataAvailabilityMode = DataAvailabilityMode::L1; + +// TODO(shahak): Remove this once we finish the sync refactor. +impl TryFrom for DataOrFin { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::StateDiffsResponse) -> Result { + match value.state_diff_message { + Some(protobuf::state_diffs_response::StateDiffMessage::ContractDiff(contract_diff)) => { + Ok(DataOrFin(Some(contract_diff.try_into()?))) + } + Some(protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + declared_class, + )) => Ok(DataOrFin(Some(declared_class.try_into()?))), + Some(protobuf::state_diffs_response::StateDiffMessage::Fin(_)) => Ok(DataOrFin(None)), + None => Err(ProtobufConversionError::MissingField { + field_description: "StateDiffsResponse::state_diff_message", + }), + } + } +} +auto_impl_try_from_vec_u8!(DataOrFin, protobuf::StateDiffsResponse); + +impl TryFrom for DataOrFin { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::StateDiffsResponse) -> Result { + match value.state_diff_message { + Some(protobuf::state_diffs_response::StateDiffMessage::ContractDiff(contract_diff)) => { + Ok(DataOrFin(Some(StateDiffChunk::ContractDiff(contract_diff.try_into()?)))) + } + Some(protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + declared_class, + )) => match declared_class.compiled_class_hash.as_ref() { + Some(_compiled_class_hash) => { + Ok(DataOrFin(Some(StateDiffChunk::DeclaredClass(declared_class.try_into()?)))) + } + None => Ok(DataOrFin(Some(StateDiffChunk::DeprecatedDeclaredClass( + declared_class.try_into()?, + )))), + }, + Some(protobuf::state_diffs_response::StateDiffMessage::Fin(_)) => Ok(DataOrFin(None)), + None => Err(ProtobufConversionError::MissingField { + field_description: "StateDiffsResponse::state_diff_message", + }), + } + } +} + +impl From> for protobuf::StateDiffsResponse { + fn from(value: DataOrFin) -> Self { + let state_diff_message = match value.0 { + Some(StateDiffChunk::ContractDiff(contract_diff)) => { + protobuf::state_diffs_response::StateDiffMessage::ContractDiff(contract_diff.into()) + } + Some(StateDiffChunk::DeclaredClass(declared_class)) => { + protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + declared_class.into(), + ) + } + Some(StateDiffChunk::DeprecatedDeclaredClass(deprecated_declared_class)) => { + protobuf::state_diffs_response::StateDiffMessage::DeclaredClass( + deprecated_declared_class.into(), + ) + } + None => protobuf::state_diffs_response::StateDiffMessage::Fin(protobuf::Fin {}), + }; + protobuf::StateDiffsResponse { state_diff_message: Some(state_diff_message) } + } +} + +impl TryFrom for ThinStateDiff { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::ContractDiff) -> Result { + let contract_address = value + .address + .ok_or(ProtobufConversionError::MissingField { + field_description: "ContractDiff::address", + })? + .try_into()?; + + let deployed_contracts = value + .class_hash + .map(|hash| { + Ok::<_, ProtobufConversionError>(IndexMap::from_iter([( + contract_address, + ClassHash(hash.try_into()?), + )])) + }) + .transpose()? + .unwrap_or_default(); + + let storage_diffs = if value.values.is_empty() { + IndexMap::new() + } else { + let storage_values = value + .values + .into_iter() + .map(|stored_value| stored_value.try_into()) + .collect::, _>>()?; + IndexMap::from_iter([(contract_address, storage_values)]) + }; + + let nonces = value + .nonce + .map(|nonce| { + Ok::<_, ProtobufConversionError>(IndexMap::from_iter([( + contract_address, + Nonce(nonce.try_into()?), + )])) + }) + .transpose()? + .unwrap_or_default(); + + // TODO(shahak): Use the domain field once Starknet supports volition. + + Ok(ThinStateDiff { + deployed_contracts, + storage_diffs, + nonces, + // These two fields come from DeclaredClass messages. + declared_classes: Default::default(), + deprecated_declared_classes: Default::default(), + // The p2p specs doesn't separate replaced classes from deployed contracts. In RPC v0.8 + // the node will stop separating them as well. Until then nodes syncing from + // P2P won't be able to separate replaced classes from deployed contracts correctly + replaced_classes: Default::default(), + }) + } +} + +impl TryFrom for ThinStateDiff { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::DeclaredClass) -> Result { + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclaredClass::class_hash", + })? + .try_into()?, + ); + + // According to the P2P specs, if compiled_class_hash is missing, the declared class is a + // cairo-0 class. + match value.compiled_class_hash { + Some(compiled_class_hash) => Ok(ThinStateDiff { + declared_classes: IndexMap::from_iter([( + class_hash, + CompiledClassHash(compiled_class_hash.try_into()?), + )]), + ..Default::default() + }), + None => Ok(ThinStateDiff { + deprecated_declared_classes: vec![class_hash], + ..Default::default() + }), + } + } +} + +impl TryFrom for (StorageKey, StarkFelt) { + type Error = ProtobufConversionError; + fn try_from(entry: protobuf::ContractStoredValue) -> Result { + let key_felt = + StarkFelt::try_from(entry.key.ok_or(ProtobufConversionError::MissingField { + field_description: "ContractStoredValue::key", + })?)?; + let key = StorageKey(key_felt.try_into().map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + // TODO(shahak): Check if the type in the protobuf of the field + // ContractStoredValue::key should be changed into a PatriciaKey which has a + // slightly lower bound than Felt. + type_description: "Felt252", + value_as_str: format!("{key_felt:?}"), + } + })?); + let value = + StarkFelt::try_from(entry.value.ok_or(ProtobufConversionError::MissingField { + field_description: "ContractStoredValue::value", + })?)?; + Ok((key, value)) + } +} + +// TODO(shahak): Erase this once network stops using it. +impl TryFrom for Query { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::StateDiffsRequest) -> Result { + Ok(StateDiffQuery::try_from(value)?.0) + } +} + +impl TryFrom for StateDiffQuery { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::StateDiffsRequest) -> Result { + Ok(StateDiffQuery( + value + .iteration + .ok_or(ProtobufConversionError::MissingField { + field_description: "StateDiffsRequest::iteration", + })? + .try_into()?, + )) + } +} + +// TODO(shahak): Erase this once network stops using it. +impl From for protobuf::StateDiffsRequest { + fn from(value: Query) -> Self { + protobuf::StateDiffsRequest { iteration: Some(value.into()) } + } +} + +impl From for protobuf::StateDiffsRequest { + fn from(value: StateDiffQuery) -> Self { + protobuf::StateDiffsRequest { iteration: Some(value.0.into()) } + } +} + +auto_impl_into_and_try_from_vec_u8!(StateDiffQuery, protobuf::StateDiffsRequest); + +impl TryFrom for ContractDiff { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::ContractDiff) -> Result { + let contract_address = value + .address + .ok_or(ProtobufConversionError::MissingField { + field_description: "ContractDiff::address", + })? + .try_into()?; + + // class_hash can be None if the contract wasn't deployed in this block + let class_hash = value + .class_hash + .map(|class_hash| Ok::<_, ProtobufConversionError>(ClassHash(class_hash.try_into()?))) + .transpose()?; + + // nonce can be None if it wasn't updated in this block + let nonce = value + .nonce + .map(|nonce| Ok::<_, ProtobufConversionError>(Nonce(nonce.try_into()?))) + .transpose()?; + + let storage_diffs = value + .values + .into_iter() + .map(|stored_value| stored_value.try_into()) + .collect::, _>>()?; + + Ok(ContractDiff { contract_address, class_hash, nonce, storage_diffs }) + } +} + +impl From for protobuf::ContractDiff { + fn from(value: ContractDiff) -> Self { + let contract_address = Some(value.contract_address.into()); + let class_hash = value.class_hash.map(|hash| hash.0.into()); + let nonce = value.nonce.map(|nonce| nonce.0.into()); + let values = value + .storage_diffs + .into_iter() + .map(|(key, value)| protobuf::ContractStoredValue { + key: Some((*key.0.key()).into()), + value: Some(value.into()), + }) + .collect(); + let domain = volition_domain_to_enum_int(DOMAIN); + protobuf::ContractDiff { address: contract_address, class_hash, nonce, values, domain } + } +} +impl TryFrom for DeclaredClass { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::DeclaredClass) -> Result { + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclaredClass::class_hash", + })? + .try_into()?, + ); + let compiled_class_hash = CompiledClassHash( + value + .compiled_class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclaredClass::compiled_class_hash", + })? + .try_into()?, + ); + Ok(DeclaredClass { class_hash, compiled_class_hash }) + } +} + +impl From for protobuf::DeclaredClass { + fn from(value: DeclaredClass) -> Self { + protobuf::DeclaredClass { + class_hash: Some(value.class_hash.0.into()), + compiled_class_hash: Some(value.compiled_class_hash.0.into()), + } + } +} + +impl TryFrom for DeprecatedDeclaredClass { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::DeclaredClass) -> Result { + Ok(DeprecatedDeclaredClass { + class_hash: ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclaredClass::class_hash", + })? + .try_into()?, + ), + }) + } +} + +impl From for protobuf::DeclaredClass { + fn from(value: DeprecatedDeclaredClass) -> Self { + protobuf::DeclaredClass { + class_hash: Some(value.class_hash.0.into()), + compiled_class_hash: None, + } + } +} diff --git a/crates/papyrus_protobuf/src/converters/transaction.rs b/crates/papyrus_protobuf/src/converters/transaction.rs new file mode 100644 index 00000000000..97ee1a13a2f --- /dev/null +++ b/crates/papyrus_protobuf/src/converters/transaction.rs @@ -0,0 +1,1220 @@ +use std::convert::{TryFrom, TryInto}; + +use prost::Message; +use starknet_api::core::{ClassHash, CompiledClassHash, EntryPointSelector, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::transaction::{ + AccountDeploymentData, + Calldata, + ContractAddressSalt, + DeclareTransaction, + DeclareTransactionV0V1, + DeclareTransactionV2, + DeclareTransactionV3, + DeployAccountTransaction, + DeployAccountTransactionV1, + DeployAccountTransactionV3, + DeployTransaction, + Fee, + InvokeTransaction, + InvokeTransactionV0, + InvokeTransactionV1, + InvokeTransactionV3, + L1HandlerTransaction, + PaymasterData, + Resource, + ResourceBounds, + ResourceBoundsMapping, + Tip, + Transaction, + TransactionOutput, + TransactionSignature, + TransactionVersion, +}; + +use super::common::{ + enum_int_to_volition_domain, + try_from_starkfelt_to_u128, + try_from_starkfelt_to_u32, + volition_domain_to_enum_int, +}; +use super::ProtobufConversionError; +use crate::sync::DataOrFin; +use crate::{auto_impl_into_and_try_from_vec_u8, protobuf}; + +impl TryFrom for DataOrFin<(Transaction, TransactionOutput)> { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::TransactionsResponse) -> Result { + let Some(transaction_message) = value.transaction_message else { + return Err(ProtobufConversionError::MissingField { + field_description: "TransactionsResponse::transaction_message", + }); + }; + + match transaction_message { + protobuf::transactions_response::TransactionMessage::TransactionWithReceipt( + tx_with_receipt, + ) => { + let result: (Transaction, TransactionOutput) = tx_with_receipt.try_into()?; + Ok(DataOrFin(Some(result))) + } + protobuf::transactions_response::TransactionMessage::Fin(_) => Ok(DataOrFin(None)), + } + } +} +impl From> for protobuf::TransactionsResponse { + fn from(value: DataOrFin<(Transaction, TransactionOutput)>) -> Self { + match value.0 { + Some((transaction, output)) => protobuf::TransactionsResponse { + transaction_message: Some( + protobuf::transactions_response::TransactionMessage::TransactionWithReceipt( + protobuf::TransactionWithReceipt::from((transaction, output)), + ), + ), + }, + None => protobuf::TransactionsResponse { + transaction_message: Some( + protobuf::transactions_response::TransactionMessage::Fin(protobuf::Fin {}), + ), + }, + } + } +} + +auto_impl_into_and_try_from_vec_u8!( + DataOrFin<(Transaction, TransactionOutput)>, + protobuf::TransactionsResponse +); + +impl TryFrom for (Transaction, TransactionOutput) { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::TransactionWithReceipt) -> Result { + let transaction = Transaction::try_from(value.transaction.ok_or( + ProtobufConversionError::MissingField { + field_description: "TransactionWithReceipt::transaction", + }, + )?)?; + + let output = TransactionOutput::try_from(value.receipt.ok_or( + ProtobufConversionError::MissingField { + field_description: "TransactionWithReceipt::output", + }, + )?)?; + Ok((transaction, output)) + } +} + +impl From<(Transaction, TransactionOutput)> for protobuf::TransactionWithReceipt { + fn from(value: (Transaction, TransactionOutput)) -> Self { + let transaction = value.0.into(); + let mut receipt = value.1.into(); + set_price_unit_based_on_transaction(&mut receipt, &transaction); + Self { transaction: Some(transaction), receipt: Some(receipt) } + } +} + +impl TryFrom for Transaction { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::Transaction) -> Result { + let txn = value.txn.ok_or(ProtobufConversionError::MissingField { + field_description: "Transaction::txn", + })?; + + Ok(match txn { + protobuf::transaction::Txn::DeclareV0(declare_v0) => Transaction::Declare( + DeclareTransaction::V0(DeclareTransactionV0V1::try_from(declare_v0)?), + ), + protobuf::transaction::Txn::DeclareV1(declare_v1) => Transaction::Declare( + DeclareTransaction::V1(DeclareTransactionV0V1::try_from(declare_v1)?), + ), + protobuf::transaction::Txn::DeclareV2(declare_v2) => Transaction::Declare( + DeclareTransaction::V2(DeclareTransactionV2::try_from(declare_v2)?), + ), + protobuf::transaction::Txn::DeclareV3(declare_v3) => Transaction::Declare( + DeclareTransaction::V3(DeclareTransactionV3::try_from(declare_v3)?), + ), + protobuf::transaction::Txn::Deploy(deploy) => { + Transaction::Deploy(DeployTransaction::try_from(deploy)?) + } + protobuf::transaction::Txn::DeployAccountV1(deploy_account_v1) => { + Transaction::DeployAccount(DeployAccountTransaction::V1( + DeployAccountTransactionV1::try_from(deploy_account_v1)?, + )) + } + protobuf::transaction::Txn::DeployAccountV3(deploy_account_v3) => { + Transaction::DeployAccount(DeployAccountTransaction::V3( + DeployAccountTransactionV3::try_from(deploy_account_v3)?, + )) + } + protobuf::transaction::Txn::InvokeV0(invoke_v0) => Transaction::Invoke( + InvokeTransaction::V0(InvokeTransactionV0::try_from(invoke_v0)?), + ), + protobuf::transaction::Txn::InvokeV1(invoke_v1) => Transaction::Invoke( + InvokeTransaction::V1(InvokeTransactionV1::try_from(invoke_v1)?), + ), + protobuf::transaction::Txn::InvokeV3(invoke_v3) => Transaction::Invoke( + InvokeTransaction::V3(InvokeTransactionV3::try_from(invoke_v3)?), + ), + protobuf::transaction::Txn::L1Handler(l1_handler) => { + Transaction::L1Handler(L1HandlerTransaction::try_from(l1_handler)?) + } + }) + } +} + +impl From for protobuf::Transaction { + fn from(value: Transaction) -> Self { + match value { + Transaction::Declare(DeclareTransaction::V0(declare_v0)) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeclareV0(declare_v0.into())), + }, + Transaction::Declare(DeclareTransaction::V1(declare_v1)) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeclareV1(declare_v1.into())), + }, + Transaction::Declare(DeclareTransaction::V2(declare_v2)) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeclareV2(declare_v2.into())), + }, + Transaction::Declare(DeclareTransaction::V3(declare_v3)) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeclareV3(declare_v3.into())), + }, + Transaction::Deploy(deploy) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::Deploy(deploy.into())), + }, + Transaction::DeployAccount(deploy_account) => match deploy_account { + DeployAccountTransaction::V1(deploy_account_v1) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeployAccountV1( + deploy_account_v1.into(), + )), + }, + DeployAccountTransaction::V3(deploy_account_v3) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::DeployAccountV3( + deploy_account_v3.into(), + )), + }, + }, + Transaction::Invoke(invoke) => match invoke { + InvokeTransaction::V0(invoke_v0) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::InvokeV0(invoke_v0.into())), + }, + InvokeTransaction::V1(invoke_v1) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::InvokeV1(invoke_v1.into())), + }, + InvokeTransaction::V3(invoke_v3) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::InvokeV3(invoke_v3.into())), + }, + }, + Transaction::L1Handler(l1_handler) => protobuf::Transaction { + txn: Some(protobuf::transaction::Txn::L1Handler(l1_handler.into())), + }, + } + } +} + +impl TryFrom for DeployAccountTransactionV1 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeployAccountV1) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV1::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV1::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV1::nonce", + })? + .try_into()?, + ); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV1::class_hash", + })? + .try_into()?, + ); + + let contract_address_salt = ContractAddressSalt( + value + .address_salt + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV1::address_salt", + })? + .try_into()?, + ); + + let constructor_calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let constructor_calldata = Calldata(constructor_calldata.into()); + + Ok(Self { + max_fee, + signature, + nonce, + class_hash, + contract_address_salt, + constructor_calldata, + }) + } +} + +impl From for protobuf::transaction::DeployAccountV1 { + fn from(value: DeployAccountTransactionV1) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|stark_felt| stark_felt.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + address_salt: Some(value.contract_address_salt.0.into()), + calldata: value + .constructor_calldata + .0 + .iter() + .map(|calldata| (*calldata).into()) + .collect(), + } + } +} + +impl TryFrom for DeployAccountTransactionV3 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeployAccountV3) -> Result { + let resource_bounds = ResourceBoundsMapping::try_from(value.resource_bounds.ok_or( + ProtobufConversionError::MissingField { + field_description: "DeployAccountV3::resource_bounds", + }, + )?)?; + + let tip = Tip(value.tip); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV3::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV3::nonce", + })? + .try_into()?, + ); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV3::class_hash", + })? + .try_into()?, + ); + + let contract_address_salt = ContractAddressSalt( + value + .address_salt + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeployAccountV3::address_salt", + })? + .try_into()?, + ); + + let constructor_calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let constructor_calldata = Calldata(constructor_calldata.into()); + + let nonce_data_availability_mode = + enum_int_to_volition_domain(value.nonce_data_availability_mode)?; + + let fee_data_availability_mode = + enum_int_to_volition_domain(value.fee_data_availability_mode)?; + + let paymaster_data = PaymasterData( + value + .paymaster_data + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + Ok(Self { + resource_bounds, + tip, + signature, + nonce, + class_hash, + contract_address_salt, + constructor_calldata, + nonce_data_availability_mode, + fee_data_availability_mode, + paymaster_data, + }) + } +} + +impl From for protobuf::transaction::DeployAccountV3 { + fn from(value: DeployAccountTransactionV3) -> Self { + Self { + resource_bounds: Some(protobuf::ResourceBounds::from(value.resource_bounds)), + tip: value.tip.0, + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|stark_felt| stark_felt.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + address_salt: Some(value.contract_address_salt.0.into()), + calldata: value + .constructor_calldata + .0 + .iter() + .map(|calldata| (*calldata).into()) + .collect(), + nonce_data_availability_mode: volition_domain_to_enum_int( + value.nonce_data_availability_mode, + ), + fee_data_availability_mode: volition_domain_to_enum_int( + value.fee_data_availability_mode, + ), + paymaster_data: value + .paymaster_data + .0 + .iter() + .map(|paymaster_data| (*paymaster_data).into()) + .collect(), + } + } +} + +impl TryFrom for ResourceBoundsMapping { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::ResourceBounds) -> Result { + let mut resource_bounds = ResourceBoundsMapping::default(); + let Some(l1_gas) = value.l1_gas else { + return Err(ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l1_gas", + }); + }; + let max_amount_felt = StarkFelt::try_from(l1_gas.max_amount.ok_or( + ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l1_gas::max_amount", + }, + )?)?; + let max_amount = + max_amount_felt.try_into().map_err(|_| ProtobufConversionError::OutOfRangeValue { + type_description: "u64", + value_as_str: format!("{max_amount_felt:?}"), + })?; + + let max_price_per_unit_felt = StarkFelt::try_from(l1_gas.max_price_per_unit.ok_or( + ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l1_gas::max_price_per_unit", + }, + )?)?; + let max_price_per_unit = + try_from_starkfelt_to_u128(max_price_per_unit_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_price_per_unit_felt:?}"), + } + })?; + + resource_bounds + .0 + .insert(Resource::L1Gas, ResourceBounds { max_amount, max_price_per_unit }); + let Some(l2_gas) = value.l2_gas else { + return Err(ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l2_gas", + }); + }; + let max_amount_felt = StarkFelt::try_from(l2_gas.max_amount.ok_or( + ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l2_gas::max_amount", + }, + )?)?; + let max_amount = + max_amount_felt.try_into().map_err(|_| ProtobufConversionError::OutOfRangeValue { + type_description: "u64", + value_as_str: format!("{max_amount_felt:?}"), + })?; + + let max_price_per_unit_felt = StarkFelt::try_from(l2_gas.max_price_per_unit.ok_or( + ProtobufConversionError::MissingField { + field_description: "ResourceBounds::l2_gas::max_price_per_unit", + }, + )?)?; + let max_price_per_unit = + try_from_starkfelt_to_u128(max_price_per_unit_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_price_per_unit_felt:?}"), + } + })?; + resource_bounds + .0 + .insert(Resource::L2Gas, ResourceBounds { max_amount, max_price_per_unit }); + Ok(resource_bounds) + } +} + +impl From for protobuf::ResourceBounds { + fn from(value: ResourceBoundsMapping) -> Self { + let mut res = protobuf::ResourceBounds::default(); + + let resource_bounds_default = ResourceBounds::default(); + let resource_bounds_l1 = value.0.get(&Resource::L1Gas).unwrap_or(&resource_bounds_default); + + let resource_limits_l1 = protobuf::ResourceLimits { + max_amount: Some(StarkFelt::from(resource_bounds_l1.max_amount).into()), + max_price_per_unit: Some(StarkFelt::from(resource_bounds_l1.max_price_per_unit).into()), + }; + res.l1_gas = Some(resource_limits_l1); + + let resource_bounds_default = ResourceBounds::default(); + let resource_bounds_l2 = value.0.get(&Resource::L2Gas).unwrap_or(&resource_bounds_default); + + let resource_limits_l2 = protobuf::ResourceLimits { + max_amount: Some(StarkFelt::from(resource_bounds_l2.max_amount).into()), + max_price_per_unit: Some(StarkFelt::from(resource_bounds_l2.max_price_per_unit).into()), + }; + res.l2_gas = Some(resource_limits_l2); + + res + } +} + +impl TryFrom for InvokeTransactionV0 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::InvokeV0) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV0::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV0::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let contract_address = value + .address + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV0::address", + })? + .try_into()?; + + let entry_point_selector_felt = StarkFelt::try_from(value.entry_point_selector.ok_or( + ProtobufConversionError::MissingField { + field_description: "InvokeV0::entry_point_selector", + }, + )?)?; + let entry_point_selector = EntryPointSelector(entry_point_selector_felt); + + let calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let calldata = Calldata(calldata.into()); + + Ok(Self { max_fee, signature, contract_address, entry_point_selector, calldata }) + } +} + +impl From for protobuf::transaction::InvokeV0 { + fn from(value: InvokeTransactionV0) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|stark_felt| stark_felt.into()).collect(), + }), + address: Some(value.contract_address.into()), + entry_point_selector: Some(value.entry_point_selector.0.into()), + calldata: value.calldata.0.iter().map(|calldata| (*calldata).into()).collect(), + } + } +} + +impl TryFrom for InvokeTransactionV1 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::InvokeV1) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV1::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV1::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { field_description: "InvokeV1::sender" })? + .try_into()?; + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV1::nonce", + })? + .try_into()?, + ); + + let calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let calldata = Calldata(calldata.into()); + + Ok(Self { max_fee, signature, nonce, sender_address, calldata }) + } +} + +impl From for protobuf::transaction::InvokeV1 { + fn from(value: InvokeTransactionV1) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|signature| signature.into()).collect(), + }), + sender: Some(value.sender_address.into()), + nonce: Some(value.nonce.0.into()), + calldata: value.calldata.0.iter().map(|calldata| (*calldata).into()).collect(), + } + } +} + +impl TryFrom for InvokeTransactionV3 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::InvokeV3) -> Result { + let resource_bounds = ResourceBoundsMapping::try_from(value.resource_bounds.ok_or( + ProtobufConversionError::MissingField { + field_description: "InvokeV3::resource_bounds", + }, + )?)?; + + let tip = Tip(value.tip); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV3::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "InvokeV3::nonce", + })? + .try_into()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { field_description: "InvokeV3::sender" })? + .try_into()?; + + let calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let calldata = Calldata(calldata.into()); + + let nonce_data_availability_mode = + enum_int_to_volition_domain(value.nonce_data_availability_mode)?; + + let fee_data_availability_mode = + enum_int_to_volition_domain(value.fee_data_availability_mode)?; + + let paymaster_data = PaymasterData( + value + .paymaster_data + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let account_deployment_data = AccountDeploymentData( + value + .account_deployment_data + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + Ok(Self { + resource_bounds, + tip, + signature, + nonce, + sender_address, + calldata, + nonce_data_availability_mode, + fee_data_availability_mode, + paymaster_data, + account_deployment_data, + }) + } +} + +impl From for protobuf::transaction::InvokeV3 { + fn from(value: InvokeTransactionV3) -> Self { + Self { + resource_bounds: Some(protobuf::ResourceBounds::from(value.resource_bounds)), + tip: value.tip.0, + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|signature| signature.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + sender: Some(value.sender_address.into()), + calldata: value.calldata.0.iter().map(|calldata| (*calldata).into()).collect(), + nonce_data_availability_mode: volition_domain_to_enum_int( + value.nonce_data_availability_mode, + ), + fee_data_availability_mode: volition_domain_to_enum_int( + value.fee_data_availability_mode, + ), + paymaster_data: value + .paymaster_data + .0 + .iter() + .map(|paymaster_data| (*paymaster_data).into()) + .collect(), + account_deployment_data: value + .account_deployment_data + .0 + .iter() + .map(|account_deployment_data| (*account_deployment_data).into()) + .collect(), + } + } +} + +impl TryFrom for DeclareTransactionV0V1 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeclareV0) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV0::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV0::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + // V0 transactions don't have a nonce, but the StarkNet API adds one to them + let nonce = Nonce::default(); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV0::class_hash", + })? + .try_into()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV0::sender", + })? + .try_into()?; + + Ok(Self { max_fee, signature, nonce, class_hash, sender_address }) + } +} + +impl From for protobuf::transaction::DeclareV0 { + fn from(value: DeclareTransactionV0V1) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|stark_felt| stark_felt.into()).collect(), + }), + sender: Some(value.sender_address.into()), + class_hash: Some(value.class_hash.0.into()), + } + } +} + +impl TryFrom for DeclareTransactionV0V1 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeclareV1) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV1::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV1::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV1::nonce", + })? + .try_into()?, + ); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV1::class_hash", + })? + .try_into()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV1::sender", + })? + .try_into()?; + + Ok(Self { max_fee, signature, nonce, class_hash, sender_address }) + } +} + +impl From for protobuf::transaction::DeclareV1 { + fn from(value: DeclareTransactionV0V1) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|stark_felt| stark_felt.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + sender: Some(value.sender_address.into()), + } + } +} + +impl TryFrom for DeclareTransactionV2 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeclareV2) -> Result { + let max_fee_felt = + StarkFelt::try_from(value.max_fee.ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::max_fee", + })?)?; + let max_fee = Fee(try_from_starkfelt_to_u128(max_fee_felt).map_err(|_| { + ProtobufConversionError::OutOfRangeValue { + type_description: "u128", + value_as_str: format!("{max_fee_felt:?}"), + } + })?); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::nonce", + })? + .try_into()?, + ); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::class_hash", + })? + .try_into()?, + ); + + let compiled_class_hash = CompiledClassHash( + value + .compiled_class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::compiled_class_hash", + })? + .try_into()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV2::sender", + })? + .try_into()?; + + Ok(Self { max_fee, signature, nonce, class_hash, compiled_class_hash, sender_address }) + } +} + +impl From for protobuf::transaction::DeclareV2 { + fn from(value: DeclareTransactionV2) -> Self { + Self { + max_fee: Some(StarkFelt::from(value.max_fee.0).into()), + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|signature| signature.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + compiled_class_hash: Some(value.compiled_class_hash.0.into()), + sender: Some(value.sender_address.into()), + } + } +} + +impl TryFrom for DeclareTransactionV3 { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::DeclareV3) -> Result { + let resource_bounds = ResourceBoundsMapping::try_from(value.resource_bounds.ok_or( + ProtobufConversionError::MissingField { + field_description: "DeclareV3::resource_bounds", + }, + )?)?; + + let tip = Tip(value.tip); + + let signature = TransactionSignature( + value + .signature + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV3::signature", + })? + .parts + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV3::nonce", + })? + .try_into()?, + ); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV3::class_hash", + })? + .try_into()?, + ); + + let compiled_class_hash = CompiledClassHash( + value + .compiled_class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV3::compiled_class_hash", + })? + .try_into()?, + ); + + let sender_address = value + .sender + .ok_or(ProtobufConversionError::MissingField { + field_description: "DeclareV3::sender", + })? + .try_into()?; + + let nonce_data_availability_mode = + enum_int_to_volition_domain(value.nonce_data_availability_mode)?; + + let fee_data_availability_mode = + enum_int_to_volition_domain(value.fee_data_availability_mode)?; + + let paymaster_data = PaymasterData( + value + .paymaster_data + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + let account_deployment_data = AccountDeploymentData( + value + .account_deployment_data + .into_iter() + .map(StarkFelt::try_from) + .collect::, _>>()?, + ); + + Ok(Self { + resource_bounds, + tip, + signature, + nonce, + class_hash, + compiled_class_hash, + sender_address, + nonce_data_availability_mode, + fee_data_availability_mode, + paymaster_data, + account_deployment_data, + }) + } +} + +impl From for protobuf::transaction::DeclareV3 { + fn from(value: DeclareTransactionV3) -> Self { + Self { + resource_bounds: Some(protobuf::ResourceBounds::from(value.resource_bounds)), + tip: value.tip.0, + signature: Some(protobuf::AccountSignature { + parts: value.signature.0.into_iter().map(|signature| signature.into()).collect(), + }), + nonce: Some(value.nonce.0.into()), + class_hash: Some(value.class_hash.0.into()), + compiled_class_hash: Some(value.compiled_class_hash.0.into()), + sender: Some(value.sender_address.into()), + nonce_data_availability_mode: volition_domain_to_enum_int( + value.nonce_data_availability_mode, + ), + fee_data_availability_mode: volition_domain_to_enum_int( + value.fee_data_availability_mode, + ), + paymaster_data: value + .paymaster_data + .0 + .iter() + .map(|paymaster_data| (*paymaster_data).into()) + .collect(), + account_deployment_data: value + .account_deployment_data + .0 + .iter() + .map(|account_deployment_data| (*account_deployment_data).into()) + .collect(), + } + } +} + +impl TryFrom for DeployTransaction { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::Deploy) -> Result { + let version = TransactionVersion(StarkFelt::from_u128(value.version.into())); + + let class_hash = ClassHash( + value + .class_hash + .ok_or(ProtobufConversionError::MissingField { + field_description: "Deploy::class_hash", + })? + .try_into()?, + ); + + let contract_address_salt = ContractAddressSalt( + value + .address_salt + .ok_or(ProtobufConversionError::MissingField { + field_description: "Deploy::address_salt", + })? + .try_into()?, + ); + + let constructor_calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let constructor_calldata = Calldata(constructor_calldata.into()); + + Ok(Self { version, class_hash, contract_address_salt, constructor_calldata }) + } +} + +impl From for protobuf::transaction::Deploy { + fn from(value: DeployTransaction) -> Self { + Self { + version: try_from_starkfelt_to_u32(value.version.0).unwrap_or_default(), + class_hash: Some(value.class_hash.0.into()), + address_salt: Some(value.contract_address_salt.0.into()), + calldata: value + .constructor_calldata + .0 + .iter() + .map(|calldata| (*calldata).into()) + .collect(), + } + } +} + +impl TryFrom for L1HandlerTransaction { + type Error = ProtobufConversionError; + fn try_from(value: protobuf::transaction::L1HandlerV0) -> Result { + let version = TransactionVersion(StarkFelt::ZERO); + + let nonce = Nonce( + value + .nonce + .ok_or(ProtobufConversionError::MissingField { + field_description: "L1HandlerV0::nonce", + })? + .try_into()?, + ); + + let contract_address = value + .address + .ok_or(ProtobufConversionError::MissingField { + field_description: "L1HandlerV0::address", + })? + .try_into()?; + + let entry_point_selector_felt = StarkFelt::try_from(value.entry_point_selector.ok_or( + ProtobufConversionError::MissingField { + field_description: "L1HandlerV0::entry_point_selector", + }, + )?)?; + let entry_point_selector = EntryPointSelector(entry_point_selector_felt); + + let calldata = + value.calldata.into_iter().map(StarkFelt::try_from).collect::, _>>()?; + + let calldata = Calldata(calldata.into()); + + Ok(Self { version, nonce, contract_address, entry_point_selector, calldata }) + } +} + +impl From for protobuf::transaction::L1HandlerV0 { + fn from(value: L1HandlerTransaction) -> Self { + Self { + nonce: Some(value.nonce.0.into()), + address: Some(value.contract_address.into()), + entry_point_selector: Some(value.entry_point_selector.0.into()), + calldata: value.calldata.0.iter().map(|calldata| (*calldata).into()).collect(), + } + } +} + +pub fn set_price_unit_based_on_transaction( + receipt: &mut protobuf::Receipt, + transaction: &protobuf::Transaction, +) { + let price_unit = match &transaction.txn { + Some(protobuf::transaction::Txn::DeclareV1(_)) => protobuf::PriceUnit::Wei, + Some(protobuf::transaction::Txn::DeclareV2(_)) => protobuf::PriceUnit::Wei, + Some(protobuf::transaction::Txn::DeclareV3(_)) => protobuf::PriceUnit::Fri, + Some(protobuf::transaction::Txn::Deploy(_)) => protobuf::PriceUnit::Wei, + Some(protobuf::transaction::Txn::DeployAccountV1(_)) => protobuf::PriceUnit::Wei, + Some(protobuf::transaction::Txn::DeployAccountV3(_)) => protobuf::PriceUnit::Fri, + Some(protobuf::transaction::Txn::InvokeV1(_)) => protobuf::PriceUnit::Wei, + Some(protobuf::transaction::Txn::InvokeV3(_)) => protobuf::PriceUnit::Fri, + Some(protobuf::transaction::Txn::L1Handler(_)) => protobuf::PriceUnit::Wei, + _ => return, + }; + let Some(ref mut receipt_type) = receipt.r#type else { + return; + }; + + let common = match receipt_type { + protobuf::receipt::Type::Invoke(invoke) => invoke.common.as_mut(), + protobuf::receipt::Type::L1Handler(l1_handler) => l1_handler.common.as_mut(), + protobuf::receipt::Type::Declare(declare) => declare.common.as_mut(), + protobuf::receipt::Type::DeprecatedDeploy(deploy) => deploy.common.as_mut(), + protobuf::receipt::Type::DeployAccount(deploy_account) => deploy_account.common.as_mut(), + }; + + if let Some(common) = common { + common.price_unit = price_unit.into(); + } +} diff --git a/crates/papyrus_protobuf/src/lib.rs b/crates/papyrus_protobuf/src/lib.rs new file mode 100644 index 00000000000..05003d81ca1 --- /dev/null +++ b/crates/papyrus_protobuf/src/lib.rs @@ -0,0 +1,6 @@ +// TODO(shahak): Internalize this once network doesn't depend on protobuf. +pub mod converters; +// TODO(shahak): Internalize this once network doesn't depend on protobuf. +pub mod consensus; +pub mod protobuf; +pub mod sync; diff --git a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/common.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/common.proto similarity index 89% rename from crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/common.proto rename to crates/papyrus_protobuf/src/proto/p2p/proto/common.proto index 22da7a272cc..63376061f2a 100644 --- a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/common.proto +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/common.proto @@ -29,16 +29,15 @@ message ConsensusSignature { Felt252 r = 1; Felt252 s = 2; } - -message Merkle { +message Patricia { uint64 n_leaves = 1; // needed to know the height, so as to how many nodes to expect in a proof. // and also when receiving all leaves, how many to expect Hash root = 2; } -message Patricia { - uint32 height = 1; - Hash root = 2; +message StateDiffCommitment { + uint64 state_diff_length = 1; + Hash root = 2; } message BlockID { @@ -51,6 +50,11 @@ enum L1DataAvailabilityMode { Blob = 1; } +enum VolitionDomain { + L1 = 0; + L2 = 1; +} + message Iteration { enum Direction { Forward = 0; diff --git a/crates/papyrus_protobuf/src/proto/p2p/proto/consensus.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/consensus.proto new file mode 100644 index 00000000000..024b9cf3f9d --- /dev/null +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/consensus.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; +import "p2p/proto/transaction.proto"; +import "p2p/proto/common.proto"; + +message Proposal { + uint64 height = 1; + Address proposer = 2; + repeated Transaction transactions = 3; + Hash block_hash = 4; +} \ No newline at end of file diff --git a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/header.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/header.proto similarity index 57% rename from crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/header.proto rename to crates/papyrus_protobuf/src/proto/p2p/proto/header.proto index f36f00cbe0c..a3af9cc1d9d 100644 --- a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/header.proto +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/header.proto @@ -1,6 +1,5 @@ syntax = "proto3"; import "p2p/proto/common.proto"; -import "p2p/proto/state.proto"; // Note: commitments may change to be for the previous blocks like comet/tendermint // hash of block header sent to L1 @@ -10,21 +9,22 @@ message SignedBlockHeader { uint64 number = 3; // This can be deduced from context. We can consider removing this field. uint64 time = 4; // Encoded in Unix time. Address sequencer_address = 5; - Patricia state = 6; // hash of contract and class patricia tries. Same as in L1. Later more trees will be included - Hash state_diff_commitment = 7; // The state diff commitment returned by the Starknet Feeder Gateway + Hash state_root = 6; // Patricia root of contract and class patricia tries. Each of those tries are of height 251. Same as in L1. Later more trees will be included + StateDiffCommitment state_diff_commitment = 7; // The state diff commitment returned by the Starknet Feeder Gateway // For more info, see https://community.starknet.io/t/introducing-p2p-authentication-and-mismatch-resolution-in-v0-12-2/97993 - uint64 state_diff_length = 8; // Equal to num_storage_diffs + num_nonce_updates + num_deployed_contracts + num_declared_classes + num_old_declared_classes - Merkle transactions = 9; // By order of execution. TBD: required? the client can execute (powerful machine) and match state diff - Merkle events = 10; // By order of issuance. TBD: in receipts? - Merkle receipts = 11; // By order of issuance. - string protocol_version = 12; // Starknet version - Uint128 gas_price_fri = 13; - Uint128 gas_price_wei = 14; - Uint128 data_gas_price_fri = 15; - Uint128 data_gas_price_wei = 16; - L1DataAvailabilityMode l1_data_availability_mode = 17; + // The leaves contain a hash of the transaction hash and transaction signature. + Patricia transactions = 8; // By order of execution. TBD: required? the client can execute (powerful machine) and match state diff + Patricia events = 9; // By order of issuance. TBD: in receipts? + Hash receipts = 10; // By order of issuance. This is a patricia root. No need for length because it's the same length as transactions. + string protocol_version = 11; // Starknet version + Uint128 gas_price_fri = 12; + Uint128 gas_price_wei = 13; + Uint128 data_gas_price_fri = 14; + Uint128 data_gas_price_wei = 15; + L1DataAvailabilityMode l1_data_availability_mode = 16; // for now, we assume a small consensus, so this fits in 1M. Else, these will be repeated and extracted from this message. - repeated ConsensusSignature signatures = 18; // can be more explicit here about the signature structure as this is not part of account abstraction + repeated ConsensusSignature signatures = 17; + // can be more explicit here about the signature structure as this is not part of account abstraction } // sent to all peers (except the ones this was received from, if any). diff --git a/crates/papyrus_protobuf/src/proto/p2p/proto/receipt.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/receipt.proto new file mode 100644 index 00000000000..3d7f4fc6985 --- /dev/null +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/receipt.proto @@ -0,0 +1,78 @@ +syntax = "proto3"; +import "p2p/proto/common.proto"; + +message MessageToL1 { + Felt252 from_address = 2; + repeated Felt252 payload = 3; + EthereumAddress to_address = 4; +} + +enum PriceUnit { + Wei = 0; + Fri = 1; +} + +message EthereumAddress { + bytes elements = 1; +} + +message Receipt { + message ExecutionResources { + message BuiltinCounter { + uint32 bitwise = 1; + uint32 ecdsa = 2; + uint32 ec_op = 3; + uint32 pedersen = 4; + uint32 range_check = 5; + uint32 poseidon = 6; + uint32 keccak = 7; + uint32 output = 8; + } + + BuiltinCounter builtins = 1; + uint32 steps = 2; + uint32 memory_holes = 3; + Felt252 l1_gas = 4; + Felt252 l1_data_gas = 5; + } + + message Common { + Felt252 actual_fee = 2; + PriceUnit price_unit = 3; + repeated MessageToL1 messages_sent = 4; + ExecutionResources execution_resources = 5; + optional string revert_reason = 6; + } + + + message Invoke { + Common common = 1; + } + + message L1Handler { + Common common = 1; + Hash msg_hash = 2; + } + + message Declare { + Common common = 1; + } + + message Deploy { + Common common = 1; + Felt252 contract_address = 2; + } + + message DeployAccount { + Common common = 1; + Felt252 contract_address = 2; + } + + oneof type { + Invoke invoke = 1; + L1Handler l1_handler = 2; + Declare declare = 3; + Deploy deprecated_deploy = 4; + DeployAccount deploy_account = 5; + } +} \ No newline at end of file diff --git a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/state.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/state.proto similarity index 90% rename from crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/state.proto rename to crates/papyrus_protobuf/src/proto/p2p/proto/state.proto index d5462153c0d..132a4a87128 100644 --- a/crates/papyrus_network/src/protobuf_messages/proto/p2p/proto/state.proto +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/state.proto @@ -12,8 +12,8 @@ message ContractDiff { Address address = 1; optional Felt252 nonce = 2; // Present only if the nonce was updated optional Hash class_hash = 3; // Present only if the contract was deployed or replaced in this block. - repeated ContractStoredValue values = 5; - uint32 domain = 6; // volition state domain + repeated ContractStoredValue values = 4; + VolitionDomain domain = 5; } message DeclaredClass { @@ -29,7 +29,7 @@ message StateDiffsRequest { message StateDiffsResponse { // All of the messages related to a block need to be sent before a message from the next block is sent. oneof state_diff_message { - ContractDiff contract_diff = 1; // Multiple contract diffs for the same contract may appear continuously if the diff is too large. + ContractDiff contract_diff = 1; // Multiple contract diffs for the same contract may appear continuously if the diff is too large or if it's more convenient. DeclaredClass declared_class = 2; Fin fin = 3; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its state diff. } diff --git a/crates/papyrus_protobuf/src/proto/p2p/proto/transaction.proto b/crates/papyrus_protobuf/src/proto/p2p/proto/transaction.proto new file mode 100644 index 00000000000..ca0ad1be3cd --- /dev/null +++ b/crates/papyrus_protobuf/src/proto/p2p/proto/transaction.proto @@ -0,0 +1,162 @@ +syntax = "proto3"; +import "p2p/proto/common.proto"; +import "p2p/proto/receipt.proto"; + +message ResourceLimits { + Felt252 max_amount = 1; + Felt252 max_price_per_unit = 2; +} + +message ResourceBounds { + ResourceLimits l1_gas = 1; + ResourceLimits l2_gas = 2; +} + +message AccountSignature { + repeated Felt252 parts = 1; +} + +// This is a transaction that is already accepted in a block. Once we have a mempool, we will define +// a separate message for BroadcastedTransaction. +message Transaction +{ + message DeclareV0 { + Address sender = 1; + Felt252 max_fee = 2; + AccountSignature signature = 3; + Hash class_hash = 4; + } + + message DeclareV1 { + Address sender = 1; + Felt252 max_fee = 2; + AccountSignature signature = 3; + Hash class_hash = 4; + Felt252 nonce = 5; + } + + message DeclareV2 { + Address sender = 1; + Felt252 max_fee = 2; + AccountSignature signature = 3; + Hash class_hash = 4; + Felt252 nonce = 5; + Hash compiled_class_hash = 6; + } + + // see https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x41d1f5206ef58a443e7d3d1ca073171ec25fa75313394318fc83a074a6631c3 + message DeclareV3 { + Address sender = 1; + AccountSignature signature = 2; + Hash class_hash = 3; + Felt252 nonce = 4; + Hash compiled_class_hash = 5; + ResourceBounds resource_bounds = 6; + uint64 tip = 7; + repeated Felt252 paymaster_data = 8; + repeated Felt252 account_deployment_data = 9; + VolitionDomain nonce_data_availability_mode = 10; + VolitionDomain fee_data_availability_mode = 11; + } + + message Deploy { + Hash class_hash = 1; + Felt252 address_salt = 2; + repeated Felt252 calldata = 3; + uint32 version = 4; + } + + message DeployAccountV1 { + Felt252 max_fee = 1; + AccountSignature signature = 2; + Hash class_hash = 3; + Felt252 nonce = 4; + Felt252 address_salt = 5; + repeated Felt252 calldata = 6; + } + + // see https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x29fd7881f14380842414cdfdd8d6c0b1f2174f8916edcfeb1ede1eb26ac3ef0 + message DeployAccountV3 { + AccountSignature signature = 1; + Hash class_hash = 2; + Felt252 nonce = 3; + Felt252 address_salt = 4; + repeated Felt252 calldata = 5; + ResourceBounds resource_bounds = 6; + uint64 tip = 7; + repeated Felt252 paymaster_data = 8; + VolitionDomain nonce_data_availability_mode = 9; + VolitionDomain fee_data_availability_mode = 10; + } + + message InvokeV0 { + Felt252 max_fee = 1; + AccountSignature signature = 2; + Address address = 3; + Felt252 entry_point_selector = 4; + repeated Felt252 calldata = 5; + } + + message InvokeV1 { + Address sender = 1; + Felt252 max_fee = 2; + AccountSignature signature = 3; + repeated Felt252 calldata = 4; + Felt252 nonce = 5; + } + + // see https://external.integration.starknet.io/feeder_gateway/get_transaction?transactionHash=0x41906f1c314cca5f43170ea75d3b1904196a10101190d2b12a41cc61cfd17c + message InvokeV3 { + Address sender = 1; + AccountSignature signature = 2; + repeated Felt252 calldata = 3; + ResourceBounds resource_bounds = 4; + uint64 tip = 5; + repeated Felt252 paymaster_data = 6; + repeated Felt252 account_deployment_data = 7; + VolitionDomain nonce_data_availability_mode = 8; + VolitionDomain fee_data_availability_mode = 9; + Felt252 nonce = 10; + } + + message L1HandlerV0 { + Felt252 nonce = 1; + Address address = 2; + Felt252 entry_point_selector = 3; + repeated Felt252 calldata = 4; + } + + oneof txn { + DeclareV0 declare_v0 = 1; + DeclareV1 declare_v1 = 2; + DeclareV2 declare_v2 = 3; + DeclareV3 declare_v3 = 4; + Deploy deploy = 5; + DeployAccountV1 deploy_account_v1 = 6; + DeployAccountV3 deploy_account_v3 = 7; + InvokeV0 invoke_v0 = 8; + InvokeV1 invoke_v1 = 9; + InvokeV3 invoke_v3 = 10; + L1HandlerV0 l1_handler = 11; + } +} + +message TransactionWithReceipt { + Transaction transaction = 1; + Receipt receipt = 2; +} + +// TBD: can support a flag to return tx hashes only, good for standalone mempool to remove them, +// or any node that keeps track of transaction streaming in the consensus. +message TransactionsRequest { + Iteration iteration = 1; +} + +// Responses are sent ordered by the order given in the request. The order inside each block is +// according to the execution order. +message TransactionsResponse { + oneof transaction_message { + TransactionWithReceipt transaction_with_receipt = 1; + Fin fin = 2; // Fin is sent after the peer sent all the data or when it encountered a block that it doesn't have its transactions. + } +} \ No newline at end of file diff --git a/crates/papyrus_protobuf/src/protobuf.rs b/crates/papyrus_protobuf/src/protobuf.rs new file mode 100644 index 00000000000..3e6d84c3336 --- /dev/null +++ b/crates/papyrus_protobuf/src/protobuf.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/_.rs")); diff --git a/crates/papyrus_protobuf/src/sync.rs b/crates/papyrus_protobuf/src/sync.rs new file mode 100644 index 00000000000..f2c55004b0f --- /dev/null +++ b/crates/papyrus_protobuf/src/sync.rs @@ -0,0 +1,78 @@ +use std::fmt::Debug; + +use indexmap::IndexMap; +use starknet_api::block::{BlockHash, BlockHeader, BlockNumber, BlockSignature}; +use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce}; +use starknet_api::hash::StarkFelt; +use starknet_api::state::StorageKey; + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash)] +pub enum Direction { + #[default] + Forward, + Backward, +} + +/// This struct represents a query that can be sent to a peer. +#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)] +pub struct Query { + pub start_block: BlockHashOrNumber, + pub direction: Direction, + pub limit: u64, + pub step: u64, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub enum BlockHashOrNumber { + Hash(BlockHash), + Number(BlockNumber), +} + +impl Default for BlockHashOrNumber { + fn default() -> Self { + Self::Number(BlockNumber::default()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DataOrFin(pub Option); + +#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)] +pub struct HeaderQuery(pub Query); + +#[derive(Default, Clone, Debug, PartialEq, Eq, Hash)] +pub struct StateDiffQuery(pub Query); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SignedBlockHeader { + pub block_header: BlockHeader, + pub signatures: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ContractDiff { + pub contract_address: ContractAddress, + // Has value only if the contract was deployed or replaced in this block. + pub class_hash: Option, + // Has value only if the nonce was updated in this block. + pub nonce: Option, + pub storage_diffs: IndexMap, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeclaredClass { + pub class_hash: ClassHash, + pub compiled_class_hash: CompiledClassHash, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct DeprecatedDeclaredClass { + pub class_hash: ClassHash, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StateDiffChunk { + ContractDiff(ContractDiff), + DeclaredClass(DeclaredClass), + DeprecatedDeclaredClass(DeprecatedDeclaredClass), +} diff --git a/crates/papyrus_rpc/Cargo.toml b/crates/papyrus_rpc/Cargo.toml index cbf59946308..f7d00fe2a3b 100644 --- a/crates/papyrus_rpc/Cargo.toml +++ b/crates/papyrus_rpc/Cargo.toml @@ -9,6 +9,7 @@ license-file.workspace = true anyhow.workspace = true async-trait.workspace = true base64.workspace = true +cairo-lang-starknet-classes.workspace = true ethers.workspace = true flate2.workspace = true futures.workspace = true @@ -27,7 +28,7 @@ starknet_client = { path = "../starknet_client", version = "0.4.0-dev.2" } regex = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } tokio-stream.workspace = true @@ -55,8 +56,8 @@ pretty_assertions.workspace = true prometheus-parse.workspace = true rand_chacha.workspace = true reqwest.workspace = true -test_utils = { path = "../test_utils" } -starknet_api = { workspace = true, features = ["testing"] } +papyrus_test_utils = { path = "../papyrus_test_utils" } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } starknet_client = { path = "../starknet_client", features = ["testing"] } starknet-core.workspace = true strum.workspace = true diff --git a/crates/papyrus_rpc/src/compression_utils_test.rs b/crates/papyrus_rpc/src/compression_utils_test.rs index 881b4a36e3a..462817c9db5 100644 --- a/crates/papyrus_rpc/src/compression_utils_test.rs +++ b/crates/papyrus_rpc/src/compression_utils_test.rs @@ -1,5 +1,5 @@ +use papyrus_test_utils::read_json_file; use pretty_assertions::assert_eq; -use test_utils::read_json_file; use super::compress_and_encode; diff --git a/crates/papyrus_rpc/src/rpc_metrics/rpc_metrics_test.rs b/crates/papyrus_rpc/src/rpc_metrics/rpc_metrics_test.rs index a14fb8b44e8..caa153af0bc 100644 --- a/crates/papyrus_rpc/src/rpc_metrics/rpc_metrics_test.rs +++ b/crates/papyrus_rpc/src/rpc_metrics/rpc_metrics_test.rs @@ -9,11 +9,11 @@ use papyrus_storage::class::ClassStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::test_utils::get_test_storage; +use papyrus_test_utils::{prometheus_is_contained, send_request}; use pretty_assertions::assert_eq; use prometheus_parse::Value::Counter; use starknet_api::block::{BlockBody, BlockHeader, BlockNumber}; use starknet_api::state::ThinStateDiff; -use test_utils::{prometheus_is_contained, send_request}; use crate::rpc_metrics::{ get_method_and_version, diff --git a/crates/papyrus_rpc/src/rpc_test.rs b/crates/papyrus_rpc/src/rpc_test.rs index 0ad52556571..a4249a956ed 100644 --- a/crates/papyrus_rpc/src/rpc_test.rs +++ b/crates/papyrus_rpc/src/rpc_test.rs @@ -12,10 +12,10 @@ use jsonrpsee::types::ErrorObjectOwned; use papyrus_storage::base_layer::BaseLayerStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::test_utils::get_test_storage; +use papyrus_test_utils::get_rng; use pretty_assertions::assert_eq; use rand::seq::SliceRandom; use starknet_api::block::{BlockHash, BlockHeader, BlockNumber, BlockStatus}; -use test_utils::get_rng; use tower::BoxError; use crate::middleware::proxy_rpc_request; diff --git a/crates/papyrus_rpc/src/v0_6/api/api_impl.rs b/crates/papyrus_rpc/src/v0_6/api/api_impl.rs index 309c1b1c895..3df0956da54 100644 --- a/crates/papyrus_rpc/src/v0_6/api/api_impl.rs +++ b/crates/papyrus_rpc/src/v0_6/api/api_impl.rs @@ -515,18 +515,13 @@ impl JsonRpcServer for JsonRpcServerImpl { StarknetApiTransaction::L1Handler(tx) => tx.version, }; - let thin_tx_output = txn + let output = txn .get_transaction_output(transaction_index) .map_err(internal_server_error)? .ok_or_else(|| ErrorObjectOwned::from(TRANSACTION_HASH_NOT_FOUND))?; - let events = txn - .get_transaction_events(transaction_index) - .map_err(internal_server_error)? - .ok_or_else(|| ErrorObjectOwned::from(TRANSACTION_HASH_NOT_FOUND))?; - - let msg_hash = match thin_tx_output { - papyrus_storage::body::events::ThinTransactionOutput::L1Handler(_) => { + let msg_hash = match output { + starknet_api::transaction::TransactionOutput::L1Handler(_) => { let starknet_api::transaction::Transaction::L1Handler(tx) = tx else { panic!("tx {} should be L1 handler", transaction_hash); }; @@ -535,12 +530,7 @@ impl JsonRpcServer for JsonRpcServerImpl { _ => None, }; - let output = TransactionOutput::from_thin_transaction_output( - thin_tx_output, - tx_version, - events, - msg_hash, - ); + let output = TransactionOutput::from((output, tx_version, msg_hash)); Ok(GeneralTransactionReceipt::TransactionReceipt(TransactionReceipt { finality_status: status.into(), diff --git a/crates/papyrus_rpc/src/v0_6/api/test.rs b/crates/papyrus_rpc/src/v0_6/api/test.rs index 7f19fa517a8..fe47a214ece 100644 --- a/crates/papyrus_rpc/src/v0_6/api/test.rs +++ b/crates/papyrus_rpc/src/v0_6/api/test.rs @@ -23,6 +23,16 @@ use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::test_utils::get_test_storage; use papyrus_storage::StorageScope; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + get_test_block, + get_test_body, + get_test_state_diff, + send_request, + GetTestInstance, +}; use pretty_assertions::assert_eq; use rand::{random, RngCore}; use rand_chacha::ChaCha8Rng; @@ -95,16 +105,6 @@ use starknet_client::writer::objects::transaction::{ }; use starknet_client::writer::{MockStarknetWriter, WriterClientError, WriterClientResult}; use starknet_client::ClientError; -use test_utils::{ - auto_impl_get_test_instance, - get_number_of_variants, - get_rng, - get_test_block, - get_test_body, - get_test_state_diff, - send_request, - GetTestInstance, -}; use super::super::api::EventsChunk; use super::super::block::{Block, GeneralBlockHeader, PendingBlockHeader, ResourcePrice}; diff --git a/crates/papyrus_rpc/src/v0_6/broadcasted_transaction_test.rs b/crates/papyrus_rpc/src/v0_6/broadcasted_transaction_test.rs index 16c88e7eaf9..d40796d2e1c 100644 --- a/crates/papyrus_rpc/src/v0_6/broadcasted_transaction_test.rs +++ b/crates/papyrus_rpc/src/v0_6/broadcasted_transaction_test.rs @@ -2,6 +2,12 @@ use std::collections::HashMap; use jsonschema::JSONSchema; use lazy_static::lazy_static; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + GetTestInstance, +}; use starknet_api::core::{CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::deprecated_contract_class::{ @@ -23,7 +29,6 @@ use starknet_api::transaction::{ TransactionSignature, }; use starknet_client::writer::objects::transaction::DeprecatedContractClass; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, get_rng, GetTestInstance}; use super::super::state::{ContractClass, EntryPointByType}; use super::{ diff --git a/crates/papyrus_rpc/src/v0_6/execution_test.rs b/crates/papyrus_rpc/src/v0_6/execution_test.rs index 47729e7b548..c16f3ba6209 100644 --- a/crates/papyrus_rpc/src/v0_6/execution_test.rs +++ b/crates/papyrus_rpc/src/v0_6/execution_test.rs @@ -30,6 +30,13 @@ use papyrus_storage::compiled_class::CasmStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::StorageWriter; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + read_json_file, + GetTestInstance, +}; use pretty_assertions::assert_eq; use starknet_api::block::{ BlockBody, @@ -78,13 +85,6 @@ use starknet_client::reader::objects::transaction::{ TransactionReceipt as ClientTransactionReceipt, }; use starknet_client::reader::PendingData; -use test_utils::{ - auto_impl_get_test_instance, - get_number_of_variants, - get_rng, - read_json_file, - GetTestInstance, -}; use tokio::sync::RwLock; use super::api::api_impl::JsonRpcServerImpl; @@ -762,9 +762,14 @@ async fn trace_block_transactions_regular_and_pending() { BlockNumber(2), BlockBody { transactions: vec![tx1, tx2], - transaction_outputs: vec![starknet_api::transaction::TransactionOutput::Invoke( - starknet_api::transaction::InvokeTransactionOutput::default(), - )], + transaction_outputs: vec![ + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + ], transaction_hashes: vec![tx_hash1, tx_hash2], }, ) @@ -960,9 +965,14 @@ async fn trace_block_transactions_and_trace_transaction_execution_context() { BlockNumber(2), BlockBody { transactions: vec![tx1, tx2], - transaction_outputs: vec![starknet_api::transaction::TransactionOutput::Invoke( - starknet_api::transaction::InvokeTransactionOutput::default(), - )], + transaction_outputs: vec![ + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + ], transaction_hashes: vec![tx_hash1, tx_hash2], }, ) diff --git a/crates/papyrus_rpc/src/v0_6/transaction.rs b/crates/papyrus_rpc/src/v0_6/transaction.rs index 528af6b3d20..141103d7799 100644 --- a/crates/papyrus_rpc/src/v0_6/transaction.rs +++ b/crates/papyrus_rpc/src/v0_6/transaction.rs @@ -10,7 +10,6 @@ use ethers::core::abi::{encode_packed, Token}; use ethers::core::utils::keccak256; use jsonrpsee::types::ErrorObjectOwned; use papyrus_execution::objects::PriceUnit; -use papyrus_storage::body::events::ThinTransactionOutput; use papyrus_storage::body::BodyStorageReader; use papyrus_storage::db::TransactionKind; use papyrus_storage::StorageTxn; @@ -850,74 +849,6 @@ impl TransactionOutput { TransactionOutput::L1Handler(tx_output) => &tx_output.execution_status, } } - - pub fn from_thin_transaction_output( - thin_tx_output: ThinTransactionOutput, - tx_version: TransactionVersion, - events: Vec, - message_hash: Option, - ) -> Self { - let actual_fee = match tx_version { - TransactionVersion::ZERO | TransactionVersion::ONE | TransactionVersion::TWO => { - FeePayment { amount: thin_tx_output.actual_fee(), unit: PriceUnit::Wei } - } - TransactionVersion::THREE => { - FeePayment { amount: thin_tx_output.actual_fee(), unit: PriceUnit::Fri } - } - _ => unreachable!("Invalid transaction version."), - }; - match thin_tx_output { - ThinTransactionOutput::Declare(thin_declare) => { - TransactionOutput::Declare(DeclareTransactionOutput { - actual_fee, - messages_sent: thin_declare.messages_sent, - events, - execution_status: thin_declare.execution_status, - execution_resources: thin_declare.execution_resources.into(), - }) - } - ThinTransactionOutput::Deploy(thin_deploy) => { - TransactionOutput::Deploy(DeployTransactionOutput { - actual_fee, - messages_sent: thin_deploy.messages_sent, - events, - contract_address: thin_deploy.contract_address, - execution_status: thin_deploy.execution_status, - execution_resources: thin_deploy.execution_resources.into(), - }) - } - ThinTransactionOutput::DeployAccount(thin_deploy) => { - TransactionOutput::DeployAccount(DeployAccountTransactionOutput { - actual_fee, - messages_sent: thin_deploy.messages_sent, - events, - contract_address: thin_deploy.contract_address, - execution_status: thin_deploy.execution_status, - execution_resources: thin_deploy.execution_resources.into(), - }) - } - ThinTransactionOutput::Invoke(thin_invoke) => { - TransactionOutput::Invoke(InvokeTransactionOutput { - actual_fee, - messages_sent: thin_invoke.messages_sent, - events, - execution_status: thin_invoke.execution_status, - execution_resources: thin_invoke.execution_resources.into(), - }) - } - ThinTransactionOutput::L1Handler(thin_l1handler) => { - TransactionOutput::L1Handler(L1HandlerTransactionOutput { - actual_fee, - messages_sent: thin_l1handler.messages_sent, - events, - execution_status: thin_l1handler.execution_status, - execution_resources: thin_l1handler.execution_resources.into(), - message_hash: message_hash - .expect("Missing message hash to construct L1Handler output."), - }) - } - } - } } impl From<(starknet_api::transaction::TransactionOutput, TransactionVersion, Option)> diff --git a/crates/papyrus_rpc/src/v0_6/transaction_test.rs b/crates/papyrus_rpc/src/v0_6/transaction_test.rs index 2b0705764ff..4f6455ed782 100644 --- a/crates/papyrus_rpc/src/v0_6/transaction_test.rs +++ b/crates/papyrus_rpc/src/v0_6/transaction_test.rs @@ -1,12 +1,8 @@ -use assert_matches::assert_matches; -use camelpaste::paste; -use papyrus_storage::body::events::{ - ThinDeclareTransactionOutput, - ThinDeployAccountTransactionOutput, - ThinDeployTransactionOutput, - ThinInvokeTransactionOutput, - ThinL1HandlerTransactionOutput, - ThinTransactionOutput, +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + GetTestInstance, }; use pretty_assertions::assert_eq; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey}; @@ -26,7 +22,6 @@ use starknet_api::transaction::{ }; use starknet_api::{calldata, contract_address, patricia_key, stark_felt}; use starknet_client::writer::objects::transaction as client_transaction; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, get_rng, GetTestInstance}; use super::super::transaction::{L1HandlerMsgHash, L1L2MsgHash}; use super::{ @@ -38,7 +33,6 @@ use super::{ InvokeTransactionV1, InvokeTransactionV3, ResourceBoundsMapping, - TransactionOutput, TransactionVersion0, TransactionVersion1, TransactionVersion3, @@ -141,39 +135,6 @@ auto_impl_get_test_instance! { } } -macro_rules! gen_test_from_thin_transaction_output_macro { - ($variant: ident) => { - paste! { - #[tokio::test] - async fn []() { - for tx_version in [TransactionVersion::ZERO, TransactionVersion::ONE, TransactionVersion::THREE] { - let thin_output = ThinTransactionOutput::$variant([]::default()); - let output = TransactionOutput::from_thin_transaction_output(thin_output, tx_version, vec![], None); - assert_matches!(output, TransactionOutput::$variant(_)); - } - } - } - }; -} - -gen_test_from_thin_transaction_output_macro!(Declare); -gen_test_from_thin_transaction_output_macro!(Deploy); -gen_test_from_thin_transaction_output_macro!(DeployAccount); -gen_test_from_thin_transaction_output_macro!(Invoke); - -#[tokio::test] -async fn from_thin_transaction_output_l1handler() { - let thin_output = ThinTransactionOutput::L1Handler(ThinL1HandlerTransactionOutput::default()); - let msg_hash = L1L2MsgHash::default(); - let output = TransactionOutput::from_thin_transaction_output( - thin_output, - TransactionVersion::ZERO, - vec![], - Some(msg_hash), - ); - assert_matches!(output, TransactionOutput::L1Handler(_)); -} - // TODO: check the conversion against the expected GW transaction. #[test] fn test_gateway_trascation_from_starknet_api_transaction() { diff --git a/crates/papyrus_rpc/src/v0_6/write_api_result_test.rs b/crates/papyrus_rpc/src/v0_6/write_api_result_test.rs index 23000f3c682..25050d2ea60 100644 --- a/crates/papyrus_rpc/src/v0_6/write_api_result_test.rs +++ b/crates/papyrus_rpc/src/v0_6/write_api_result_test.rs @@ -1,3 +1,4 @@ +use papyrus_test_utils::{auto_impl_get_test_instance, get_rng, GetTestInstance}; use serde::Serialize; use starknet_api::core::{ClassHash, ContractAddress, PatriciaKey}; use starknet_api::hash::StarkFelt; @@ -9,7 +10,6 @@ use starknet_client::writer::objects::response::{ InvokeResponse, SuccessfulStarknetErrorCode, }; -use test_utils::{auto_impl_get_test_instance, get_rng, GetTestInstance}; use super::{AddDeclareOkResult, AddDeployAccountOkResult, AddInvokeOkResult}; use crate::test_utils::{get_starknet_spec_api_schema_for_method_results, SpecFile}; diff --git a/crates/papyrus_rpc/src/v0_7/api/api_impl.rs b/crates/papyrus_rpc/src/v0_7/api/api_impl.rs index 6274502e0f3..c094e92b5a8 100644 --- a/crates/papyrus_rpc/src/v0_7/api/api_impl.rs +++ b/crates/papyrus_rpc/src/v0_7/api/api_impl.rs @@ -1,6 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use jsonrpsee::core::RpcResult; use jsonrpsee::types::ErrorObjectOwned; use jsonrpsee::RpcModule; @@ -17,6 +18,7 @@ use papyrus_execution::{ }; use papyrus_storage::body::events::{EventIndex, EventsReader}; use papyrus_storage::body::{BodyStorageReader, TransactionIndex}; +use papyrus_storage::compiled_class::CasmStorageReader; use papyrus_storage::db::{TransactionKind, RO}; use papyrus_storage::state::StateStorageReader; use papyrus_storage::{StorageError, StorageReader, StorageTxn}; @@ -1429,6 +1431,30 @@ impl JsonRpcServer for JsonRpcServerImpl { Err(err) => Err(internal_server_error(err)), } } + + #[instrument(skip(self), level = "debug", err)] + fn get_compiled_contract_class( + &self, + block_id: BlockId, + class_hash: ClassHash, + ) -> RpcResult { + let storage_txn = self.storage_reader.begin_ro_txn().map_err(internal_server_error)?; + let block_number = get_accepted_block_number(&storage_txn, block_id)?; + let class_definition_block_number = storage_txn + .get_state_reader() + .map_err(internal_server_error)? + .get_class_definition_block_number(&class_hash) + .map_err(internal_server_error)? + .ok_or_else(|| ErrorObjectOwned::from(CLASS_HASH_NOT_FOUND))?; + if class_definition_block_number > block_number { + return Err(ErrorObjectOwned::from(CLASS_HASH_NOT_FOUND)); + } + let casm = storage_txn + .get_casm(&class_hash) + .map_err(internal_server_error)? + .ok_or_else(|| ErrorObjectOwned::from(CLASS_HASH_NOT_FOUND))?; + Ok(casm) + } } async fn read_pending_data( @@ -1535,22 +1561,12 @@ fn get_non_pending_receipt( let block_hash = get_block_header_by_number(txn, block_number).map_err(internal_server_error)?.block_hash; - let thin_tx_output = txn + let output = txn .get_transaction_output(transaction_index) .map_err(internal_server_error)? .ok_or_else(|| ErrorObjectOwned::from(TRANSACTION_HASH_NOT_FOUND))?; - let events = txn - .get_transaction_events(transaction_index) - .map_err(internal_server_error)? - .ok_or_else(|| ErrorObjectOwned::from(TRANSACTION_HASH_NOT_FOUND))?; - - let output = TransactionOutput::from_thin_transaction_output( - thin_tx_output, - tx_version, - events, - msg_hash, - ); + let output = TransactionOutput::from((output, tx_version, msg_hash)); Ok(GeneralTransactionReceipt::TransactionReceipt(TransactionReceipt { finality_status: status.into(), diff --git a/crates/papyrus_rpc/src/v0_7/api/mod.rs b/crates/papyrus_rpc/src/v0_7/api/mod.rs index 794f2d4446f..0e73ec6853c 100644 --- a/crates/papyrus_rpc/src/v0_7/api/mod.rs +++ b/crates/papyrus_rpc/src/v0_7/api/mod.rs @@ -1,6 +1,7 @@ use std::collections::HashSet; use std::io::Read; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use flate2::bufread::GzDecoder; use jsonrpsee::core::RpcResult; use jsonrpsee::proc_macros::rpc; @@ -252,6 +253,14 @@ pub trait JsonRpc { &self, block_id: BlockId, ) -> RpcResult>; + + /// Returns the compiled contract class associated with the given class hash. + #[method(name = "getCompiledContractClass")] + fn get_compiled_contract_class( + &self, + block_id: BlockId, + class_hash: ClassHash, + ) -> RpcResult; } #[derive(Debug, Clone, Deserialize, Serialize)] diff --git a/crates/papyrus_rpc/src/v0_7/api/test.rs b/crates/papyrus_rpc/src/v0_7/api/test.rs index 108119d10bc..aa71deebbb8 100644 --- a/crates/papyrus_rpc/src/v0_7/api/test.rs +++ b/crates/papyrus_rpc/src/v0_7/api/test.rs @@ -6,6 +6,7 @@ use std::ops::Index; use assert_matches::assert_matches; use async_trait::async_trait; +use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use indexmap::{indexmap, IndexMap}; use itertools::Itertools; use jsonrpsee::core::Error; @@ -19,10 +20,21 @@ use papyrus_storage::base_layer::BaseLayerStorageWriter; use papyrus_storage::body::events::EventIndex; use papyrus_storage::body::{BodyStorageWriter, TransactionIndex}; use papyrus_storage::class::ClassStorageWriter; +use papyrus_storage::compiled_class::CasmStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::test_utils::get_test_storage; use papyrus_storage::StorageScope; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + get_test_block, + get_test_body, + get_test_state_diff, + send_request, + GetTestInstance, +}; use pretty_assertions::assert_eq; use rand::{random, RngCore}; use rand_chacha::ChaCha8Rng; @@ -41,6 +53,7 @@ use starknet_api::block::{ }; use starknet_api::core::{ ClassHash, + CompiledClassHash, ContractAddress, GlobalRoot, Nonce, @@ -96,16 +109,6 @@ use starknet_client::writer::objects::transaction::{ }; use starknet_client::writer::{MockStarknetWriter, WriterClientError, WriterClientResult}; use starknet_client::ClientError; -use test_utils::{ - auto_impl_get_test_instance, - get_number_of_variants, - get_rng, - get_test_block, - get_test_body, - get_test_state_diff, - send_request, - GetTestInstance, -}; use super::super::api::EventsChunk; use super::super::block::{Block, GeneralBlockHeader, PendingBlockHeader, ResourcePrice}; @@ -3558,6 +3561,47 @@ async fn get_deprecated_class_state_mutability() { assert_eq!(entry.get("stateMutability").unwrap().as_str().unwrap(), "view"); } +#[tokio::test] +async fn get_compiled_contract_class() { + let method_name = "starknet_V0_7_getCompiledContractClass"; + let (module, mut storage_writer) = get_test_rpc_server_and_storage_writer_from_params::< + JsonRpcServerImpl, + >(None, None, None, None, None); + let class_hash = ClassHash(stark_felt!("0x1")); + let casm_contract_class = CasmContractClass::get_test_instance(&mut get_rng()); + storage_writer + .begin_rw_txn() + .unwrap() + .append_state_diff( + BlockNumber(0), + starknet_api::state::ThinStateDiff { + declared_classes: IndexMap::from([(class_hash, CompiledClassHash::default())]), + ..Default::default() + }, + ) + .unwrap() + .append_casm(&class_hash, &casm_contract_class) + .unwrap() + .commit() + .unwrap(); + + let res = module + .call::<_, CasmContractClass>(method_name, (BlockId::Tag(Tag::Latest), class_hash)) + .await + .unwrap(); + assert_eq!(res, casm_contract_class); + + // Ask for an invalid class hash. + let err = module + .call::<_, CasmContractClass>( + method_name, + (BlockId::Tag(Tag::Latest), ClassHash(stark_felt!("0x2"))), + ) + .await + .unwrap_err(); + assert_matches!(err, Error::Call(err) if err == CLASS_HASH_NOT_FOUND.into()); +} + #[async_trait] trait AddTransactionTest where @@ -3886,7 +3930,14 @@ fn spec_api_methods_coverage() { }) .sorted() .collect::>(); - assert!(method_names_in_spec.eq(&implemented_method_names)); + + // Methods in the spec are a subset of the implemented methods. + assert!( + method_names_in_spec.iter().all(|method| implemented_method_names.contains(method)), + "Implemented methods: {:#?}, methods in spec: {:#?}", + implemented_method_names, + method_names_in_spec + ); } auto_impl_get_test_instance! { diff --git a/crates/papyrus_rpc/src/v0_7/broadcasted_transaction_test.rs b/crates/papyrus_rpc/src/v0_7/broadcasted_transaction_test.rs index 83e227886a5..799b9cf680c 100644 --- a/crates/papyrus_rpc/src/v0_7/broadcasted_transaction_test.rs +++ b/crates/papyrus_rpc/src/v0_7/broadcasted_transaction_test.rs @@ -2,6 +2,12 @@ use std::collections::HashMap; use jsonschema::JSONSchema; use lazy_static::lazy_static; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + GetTestInstance, +}; use starknet_api::core::{CompiledClassHash, ContractAddress, Nonce}; use starknet_api::data_availability::DataAvailabilityMode; use starknet_api::deprecated_contract_class::{ @@ -23,7 +29,6 @@ use starknet_api::transaction::{ TransactionSignature, }; use starknet_client::writer::objects::transaction::DeprecatedContractClass; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, get_rng, GetTestInstance}; use super::super::state::{ContractClass, EntryPointByType}; use super::{ diff --git a/crates/papyrus_rpc/src/v0_7/execution_test.rs b/crates/papyrus_rpc/src/v0_7/execution_test.rs index aa6bfae02aa..69817c69af5 100644 --- a/crates/papyrus_rpc/src/v0_7/execution_test.rs +++ b/crates/papyrus_rpc/src/v0_7/execution_test.rs @@ -34,6 +34,13 @@ use papyrus_storage::compiled_class::CasmStorageWriter; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::state::StateStorageWriter; use papyrus_storage::StorageWriter; +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + read_json_file, + GetTestInstance, +}; use pretty_assertions::assert_eq; use starknet_api::block::{ BlockBody, @@ -82,13 +89,6 @@ use starknet_client::reader::objects::transaction::{ TransactionReceipt as ClientTransactionReceipt, }; use starknet_client::reader::PendingData; -use test_utils::{ - auto_impl_get_test_instance, - get_number_of_variants, - get_rng, - read_json_file, - GetTestInstance, -}; use tokio::sync::RwLock; use super::api::api_impl::JsonRpcServerImpl; @@ -797,9 +797,14 @@ async fn trace_block_transactions_regular_and_pending() { BlockNumber(3), BlockBody { transactions: vec![tx1, tx2], - transaction_outputs: vec![starknet_api::transaction::TransactionOutput::Invoke( - starknet_api::transaction::InvokeTransactionOutput::default(), - )], + transaction_outputs: vec![ + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + ], transaction_hashes: vec![tx_hash1, tx_hash2], }, ) @@ -996,9 +1001,14 @@ async fn trace_block_transactions_and_trace_transaction_execution_context() { BlockNumber(3), BlockBody { transactions: vec![tx1, tx2], - transaction_outputs: vec![starknet_api::transaction::TransactionOutput::Invoke( - starknet_api::transaction::InvokeTransactionOutput::default(), - )], + transaction_outputs: vec![ + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + starknet_api::transaction::TransactionOutput::Invoke( + starknet_api::transaction::InvokeTransactionOutput::default(), + ), + ], transaction_hashes: vec![tx_hash1, tx_hash2], }, ) diff --git a/crates/papyrus_rpc/src/v0_7/transaction.rs b/crates/papyrus_rpc/src/v0_7/transaction.rs index 850f7b9f0de..d98020c0838 100644 --- a/crates/papyrus_rpc/src/v0_7/transaction.rs +++ b/crates/papyrus_rpc/src/v0_7/transaction.rs @@ -12,7 +12,6 @@ use ethers::core::abi::{encode_packed, Token}; use ethers::core::utils::keccak256; use jsonrpsee::types::ErrorObjectOwned; use papyrus_execution::objects::PriceUnit; -use papyrus_storage::body::events::ThinTransactionOutput; use papyrus_storage::body::BodyStorageReader; use papyrus_storage::db::TransactionKind; use papyrus_storage::StorageTxn; @@ -985,74 +984,6 @@ impl TransactionOutput { TransactionOutput::L1Handler(tx_output) => &tx_output.execution_status, } } - - pub fn from_thin_transaction_output( - thin_tx_output: ThinTransactionOutput, - tx_version: TransactionVersion, - events: Vec, - message_hash: Option, - ) -> Self { - let actual_fee = match tx_version { - TransactionVersion::ZERO | TransactionVersion::ONE | TransactionVersion::TWO => { - FeePayment { amount: thin_tx_output.actual_fee(), unit: PriceUnit::Wei } - } - TransactionVersion::THREE => { - FeePayment { amount: thin_tx_output.actual_fee(), unit: PriceUnit::Fri } - } - _ => unreachable!("Invalid transaction version."), - }; - match thin_tx_output { - ThinTransactionOutput::Declare(thin_declare) => { - TransactionOutput::Declare(DeclareTransactionOutput { - actual_fee, - messages_sent: thin_declare.messages_sent, - events, - execution_status: thin_declare.execution_status, - execution_resources: thin_declare.execution_resources.into(), - }) - } - ThinTransactionOutput::Deploy(thin_deploy) => { - TransactionOutput::Deploy(DeployTransactionOutput { - actual_fee, - messages_sent: thin_deploy.messages_sent, - events, - contract_address: thin_deploy.contract_address, - execution_status: thin_deploy.execution_status, - execution_resources: thin_deploy.execution_resources.into(), - }) - } - ThinTransactionOutput::DeployAccount(thin_deploy) => { - TransactionOutput::DeployAccount(DeployAccountTransactionOutput { - actual_fee, - messages_sent: thin_deploy.messages_sent, - events, - contract_address: thin_deploy.contract_address, - execution_status: thin_deploy.execution_status, - execution_resources: thin_deploy.execution_resources.into(), - }) - } - ThinTransactionOutput::Invoke(thin_invoke) => { - TransactionOutput::Invoke(InvokeTransactionOutput { - actual_fee, - messages_sent: thin_invoke.messages_sent, - events, - execution_status: thin_invoke.execution_status, - execution_resources: thin_invoke.execution_resources.into(), - }) - } - ThinTransactionOutput::L1Handler(thin_l1handler) => { - TransactionOutput::L1Handler(L1HandlerTransactionOutput { - actual_fee, - messages_sent: thin_l1handler.messages_sent, - events, - execution_status: thin_l1handler.execution_status, - execution_resources: thin_l1handler.execution_resources.into(), - message_hash: message_hash - .expect("Missing message hash to construct L1Handler output."), - }) - } - } - } } impl From<(starknet_api::transaction::TransactionOutput, TransactionVersion, Option)> diff --git a/crates/papyrus_rpc/src/v0_7/transaction_test.rs b/crates/papyrus_rpc/src/v0_7/transaction_test.rs index 2b0705764ff..4f6455ed782 100644 --- a/crates/papyrus_rpc/src/v0_7/transaction_test.rs +++ b/crates/papyrus_rpc/src/v0_7/transaction_test.rs @@ -1,12 +1,8 @@ -use assert_matches::assert_matches; -use camelpaste::paste; -use papyrus_storage::body::events::{ - ThinDeclareTransactionOutput, - ThinDeployAccountTransactionOutput, - ThinDeployTransactionOutput, - ThinInvokeTransactionOutput, - ThinL1HandlerTransactionOutput, - ThinTransactionOutput, +use papyrus_test_utils::{ + auto_impl_get_test_instance, + get_number_of_variants, + get_rng, + GetTestInstance, }; use pretty_assertions::assert_eq; use starknet_api::core::{ClassHash, ContractAddress, EntryPointSelector, Nonce, PatriciaKey}; @@ -26,7 +22,6 @@ use starknet_api::transaction::{ }; use starknet_api::{calldata, contract_address, patricia_key, stark_felt}; use starknet_client::writer::objects::transaction as client_transaction; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, get_rng, GetTestInstance}; use super::super::transaction::{L1HandlerMsgHash, L1L2MsgHash}; use super::{ @@ -38,7 +33,6 @@ use super::{ InvokeTransactionV1, InvokeTransactionV3, ResourceBoundsMapping, - TransactionOutput, TransactionVersion0, TransactionVersion1, TransactionVersion3, @@ -141,39 +135,6 @@ auto_impl_get_test_instance! { } } -macro_rules! gen_test_from_thin_transaction_output_macro { - ($variant: ident) => { - paste! { - #[tokio::test] - async fn []() { - for tx_version in [TransactionVersion::ZERO, TransactionVersion::ONE, TransactionVersion::THREE] { - let thin_output = ThinTransactionOutput::$variant([]::default()); - let output = TransactionOutput::from_thin_transaction_output(thin_output, tx_version, vec![], None); - assert_matches!(output, TransactionOutput::$variant(_)); - } - } - } - }; -} - -gen_test_from_thin_transaction_output_macro!(Declare); -gen_test_from_thin_transaction_output_macro!(Deploy); -gen_test_from_thin_transaction_output_macro!(DeployAccount); -gen_test_from_thin_transaction_output_macro!(Invoke); - -#[tokio::test] -async fn from_thin_transaction_output_l1handler() { - let thin_output = ThinTransactionOutput::L1Handler(ThinL1HandlerTransactionOutput::default()); - let msg_hash = L1L2MsgHash::default(); - let output = TransactionOutput::from_thin_transaction_output( - thin_output, - TransactionVersion::ZERO, - vec![], - Some(msg_hash), - ); - assert_matches!(output, TransactionOutput::L1Handler(_)); -} - // TODO: check the conversion against the expected GW transaction. #[test] fn test_gateway_trascation_from_starknet_api_transaction() { diff --git a/crates/papyrus_rpc/src/v0_7/write_api_result_test.rs b/crates/papyrus_rpc/src/v0_7/write_api_result_test.rs index 02ea6163766..394cc71e28f 100644 --- a/crates/papyrus_rpc/src/v0_7/write_api_result_test.rs +++ b/crates/papyrus_rpc/src/v0_7/write_api_result_test.rs @@ -1,3 +1,4 @@ +use papyrus_test_utils::{auto_impl_get_test_instance, get_rng, GetTestInstance}; use serde::Serialize; use starknet_api::core::{ClassHash, ContractAddress, PatriciaKey}; use starknet_api::hash::StarkFelt; @@ -9,7 +10,6 @@ use starknet_client::writer::objects::response::{ InvokeResponse, SuccessfulStarknetErrorCode, }; -use test_utils::{auto_impl_get_test_instance, get_rng, GetTestInstance}; use super::{AddDeclareOkResult, AddDeployAccountOkResult, AddInvokeOkResult}; use crate::test_utils::{get_starknet_spec_api_schema_for_method_results, SpecFile}; diff --git a/crates/papyrus_storage/Cargo.toml b/crates/papyrus_storage/Cargo.toml index ea846ff1d84..3ab9595d63e 100644 --- a/crates/papyrus_storage/Cargo.toml +++ b/crates/papyrus_storage/Cargo.toml @@ -42,7 +42,7 @@ parity-scale-codec.workspace = true primitive-types.workspace = true serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } -starknet_api.workspace = true +starknet_api = {version = "0.12.0-dev.1"} tempfile = { workspace = true, optional = true } thiserror.workspace = true tracing = { workspace = true, features = ["log"] } @@ -73,5 +73,5 @@ simple_logger.workspace = true tempfile = { workspace = true } test-case.workspace = true test-log.workspace = true -test_utils = { path = "../test_utils" } +papyrus_test_utils = { path = "../papyrus_test_utils" } tokio = { workspace = true, features = ["full", "sync"] } diff --git a/crates/papyrus_storage/src/body/body_test.rs b/crates/papyrus_storage/src/body/body_test.rs index 41069c55191..0138f058ef4 100644 --- a/crates/papyrus_storage/src/body/body_test.rs +++ b/crates/papyrus_storage/src/body/body_test.rs @@ -1,11 +1,10 @@ use assert_matches::assert_matches; +use papyrus_test_utils::{get_test_block, get_test_body}; use pretty_assertions::assert_eq; use starknet_api::block::{BlockBody, BlockNumber}; use starknet_api::transaction::TransactionOffsetInBlock; use test_case::test_case; -use test_utils::{get_test_block, get_test_body}; -use crate::body::events::ThinTransactionOutput; use crate::body::{BodyStorageReader, BodyStorageWriter, TransactionIndex}; use crate::db::{DbError, KeyAlreadyExistsError}; use crate::test_utils::{get_test_storage, get_test_storage_by_scope}; @@ -92,18 +91,11 @@ async fn append_body() { expected_tx ); - let expected_tx_output = - original_index.map(|i| ThinTransactionOutput::from(tx_outputs[i].clone())); + let expected_tx_output = original_index.map(|i| tx_outputs[i].clone()); assert_eq!( txn.get_transaction_output(TransactionIndex(block_number, tx_offset)).unwrap(), expected_tx_output ); - - let expected_events = original_index.map(|i| tx_outputs[i].events().to_owned()); - assert_eq!( - txn.get_transaction_events(TransactionIndex(block_number, tx_offset)).unwrap(), - expected_events - ) } // Check transaction index by hash. @@ -167,15 +159,12 @@ async fn append_body() { // Check block transaction outputs. assert_eq!( txn.get_block_transaction_outputs(BlockNumber(0)).unwrap(), - Some(vec![ThinTransactionOutput::from(tx_outputs[0].clone())]) + Some(vec![tx_outputs[0].clone()]) ); assert_eq!(txn.get_block_transaction_outputs(BlockNumber(1)).unwrap(), Some(vec![])); assert_eq!( txn.get_block_transaction_outputs(BlockNumber(2)).unwrap(), - Some(vec![ - ThinTransactionOutput::from(tx_outputs[1].clone()), - ThinTransactionOutput::from(tx_outputs[2].clone()), - ]) + Some(vec![tx_outputs[1].clone(), tx_outputs[2].clone(),]) ); assert_eq!(txn.get_block_transaction_outputs(BlockNumber(3)).unwrap(), None); @@ -353,7 +342,6 @@ async fn revert_transactions() { assert!(reader.begin_ro_txn().unwrap().get_transaction(tx_index).unwrap().is_none()); assert!(reader.begin_ro_txn().unwrap().get_transaction_output(tx_index).unwrap().is_none()); - assert!(reader.begin_ro_txn().unwrap().get_transaction_events(tx_index).unwrap().is_none()); assert!( reader.begin_ro_txn().unwrap().get_transaction_idx_by_hash(&tx_hash).unwrap().is_none() ); diff --git a/crates/papyrus_storage/src/body/events.rs b/crates/papyrus_storage/src/body/events.rs index 16321e6e70e..6537018e45f 100644 --- a/crates/papyrus_storage/src/body/events.rs +++ b/crates/papyrus_storage/src/body/events.rs @@ -52,20 +52,18 @@ use serde::{Deserialize, Serialize}; use starknet_api::block::BlockNumber; use starknet_api::core::ContractAddress; use starknet_api::transaction::{ + Event, EventContent, EventIndexInTransactionOutput, - ExecutionResources, - Fee, - MessageToL1, - TransactionExecutionStatus, TransactionOutput, }; -use crate::body::{EventsTable, EventsTableKey, TransactionIndex}; +use super::TransactionMetadataTable; +use crate::body::{EventsTableKey, TransactionIndex}; use crate::db::serialization::{NoVersionValueWrapper, VersionZeroWrapper}; -use crate::db::table_types::{DbCursor, DbCursorTrait, SimpleTable, Table}; +use crate::db::table_types::{DbCursor, DbCursorTrait, NoValue, SimpleTable, Table}; use crate::db::{DbTransaction, RO}; -use crate::{StorageResult, StorageTxn}; +use crate::{FileHandlers, StorageResult, StorageTxn, TransactionMetadata}; /// An identifier of an event. #[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize, PartialOrd, Ord)] @@ -111,11 +109,14 @@ impl<'txn, 'env> EventsReader<'txn, 'env> for StorageTxn<'env, RO> { } } +// TODO(dvir): add transaction hash to the return value. In the RPC when returning events this is +// with the transaction hash. We can do it efficiently here because we anyway read the relevant +// entry in the transaction_metadata table.. #[allow(missing_docs)] /// A wrapper of two iterators [`EventIterByContractAddress`] and [`EventIterByEventIndex`]. pub enum EventIter<'txn, 'env> { - ByContractAddress(EventIterByContractAddress<'txn>), - ByEventIndex(EventIterByEventIndex<'txn, 'env>), + ByContractAddress(EventIterByContractAddress<'env, 'txn>), + ByEventIndex(EventIterByEventIndex<'txn>), } /// This iterator is a wrapper of two iterators [`EventIterByContractAddress`] @@ -123,7 +124,7 @@ pub enum EventIter<'txn, 'env> { /// With this wrapper we can execute the same code, regardless the /// type of iteration used. impl Iterator for EventIter<'_, '_> { - type Item = EventsTableKeyValue; + type Item = (EventsTableKey, EventContent); fn next(&mut self) -> Option { match self { @@ -136,20 +137,59 @@ impl Iterator for EventIter<'_, '_> { /// This iterator goes over the events in the order of the events table key. /// That is, the events iterated first by the contract address and then by the event index. -pub struct EventIterByContractAddress<'txn> { - current: Option, +pub struct EventIterByContractAddress<'env, 'txn> { + txn: &'txn DbTransaction<'env, RO>, + file_handles: &'txn FileHandlers, + // This value is the next event to return. If it is None there are no more events. + current: Option, + // The current transaction output. This is None only at the beginning of the iteration and + // filled with the first transaction output. + current_tx: Option<(TransactionIndex, TransactionOutput)>, cursor: EventsTableCursor<'txn>, + transaction_metadata_table: TransactionMetadataTable<'env>, } -impl EventIterByContractAddress<'_> { +impl<'env, 'txn> EventIterByContractAddress<'env, 'txn> { /// Returns the next event. If there are no more events, returns None. /// /// # Errors /// Returns [`StorageError`](crate::StorageError) if there was an error. - fn next(&mut self) -> StorageResult> { - let res = self.current.take(); - self.current = self.cursor.next()?; - Ok(res) + fn next(&mut self) -> StorageResult> { + let Some((contract_address, EventIndex(tx_index, event_offset))) = self.current.take() + else { + return Ok(None); + }; + if self.current_tx.is_none() + || tx_index + != self.current_tx.as_ref().expect("The None case was checked previously.").0 + { + let Some(tx_metadata) = self.transaction_metadata_table.get(self.txn, &tx_index)? + else { + return Ok(None); + }; + self.current_tx = Some(( + tx_index, + self.file_handles + .get_transaction_output_unchecked(tx_metadata.tx_output_location)?, + )); + } + + self.current = self.cursor.next()?.map(|(key, _)| key); + + let key = (contract_address, EventIndex(tx_index, event_offset)); + // TODO(dvir): don't clone here the event content. + let content = self + .current_tx + .as_ref() + .expect( + "The current transaction was initialized with Some previously in this function.", + ) + .1 + .events()[event_offset.0] + .content + .clone(); + + Ok(Some((key, content))) } } @@ -157,32 +197,32 @@ impl EventIterByContractAddress<'_> { /// That is, the events are iterated by the order they are emitted. /// First by the block number, then by the transaction offset in the block, /// and finally, by the event index in the transaction output. -pub struct EventIterByEventIndex<'txn, 'env> { - txn: &'txn DbTransaction<'env, RO>, - tx_current: Option, - tx_cursor: TransactionOutputsTableCursor<'txn>, - events_table: EventsTable<'env>, +pub struct EventIterByEventIndex<'txn> { + file_handlers: &'txn FileHandlers, + tx_current: Option<(TransactionIndex, TransactionOutput)>, + tx_cursor: TransactionMetadataTableCursor<'txn>, event_index_in_tx_current: EventIndexInTransactionOutput, to_block_number: BlockNumber, } -impl EventIterByEventIndex<'_, '_> { +impl EventIterByEventIndex<'_> { /// Returns the next event. If there are no more events, returns None. /// /// # Errors /// Returns [`StorageError`](crate::StorageError) if there was an error. - fn next(&mut self) -> StorageResult> { + fn next(&mut self) -> StorageResult> { let Some((tx_index, tx_output)) = &self.tx_current else { return Ok(None) }; - let Some(address) = - tx_output.events_contract_addresses_as_ref().get(self.event_index_in_tx_current.0) + let Some(Event { from_address, content }) = + tx_output.events().get(self.event_index_in_tx_current.0) else { return Ok(None); }; - let key = (*address, EventIndex(*tx_index, self.event_index_in_tx_current)); - let Some(content) = self.events_table.get(self.txn, &key)? else { return Ok(None) }; + let key = (*from_address, EventIndex(*tx_index, self.event_index_in_tx_current)); + // TODO(dvir): don't clone here the event content. + let content = content.clone(); self.event_index_in_tx_current.0 += 1; self.find_next_event_by_event_index()?; - Ok(Some((key, content))) + Ok(Some((key, content.clone()))) } /// Finds the event that corresponds to the first event index greater than or equals to the @@ -199,14 +239,21 @@ impl EventIterByEventIndex<'_, '_> { break; } // Checks if there's an event in the current event index. - if tx_output.events_contract_addresses_as_ref().len() > self.event_index_in_tx_current.0 - { + if tx_output.events().len() > self.event_index_in_tx_current.0 { break; } // There are no more events in the current transaction, so we go over the rest of the // transactions until we find an event. - self.tx_current = self.tx_cursor.next()?; + let Some((tx_index, tx_metadata)) = self.tx_cursor.next()? else { + self.tx_current = None; + return Ok(()); + }; + self.tx_current = Some(( + tx_index, + self.file_handlers + .get_transaction_output_unchecked(tx_metadata.tx_output_location)?, + )); self.event_index_in_tx_current = EventIndexInTransactionOutput(0); } @@ -214,7 +261,10 @@ impl EventIterByEventIndex<'_, '_> { } } -impl<'txn, 'env> StorageTxn<'env, RO> { +impl<'txn, 'env> StorageTxn<'env, RO> +where + 'env: 'txn, +{ /// Returns an events iterator that iterates events by the events table key from the given key. /// /// # Arguments @@ -225,11 +275,19 @@ impl<'txn, 'env> StorageTxn<'env, RO> { fn iter_events_by_contract_address( &'env self, key: EventsTableKey, - ) -> StorageResult> { + ) -> StorageResult> { + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; let events_table = self.open_table(&self.tables.events)?; let mut cursor = events_table.cursor(&self.txn)?; - let current = cursor.lower_bound(&key)?; - Ok(EventIterByContractAddress { current, cursor }) + let current = cursor.lower_bound(&key)?.map(|(key, _)| key); + Ok(EventIterByContractAddress { + txn: &self.txn, + file_handles: &self.file_handlers, + current, + current_tx: None, + cursor, + transaction_metadata_table, + }) } /// Returns an events iterator that iterates events by event index from the given event index. @@ -245,17 +303,23 @@ impl<'txn, 'env> StorageTxn<'env, RO> { &'env self, event_index: EventIndex, to_block_number: BlockNumber, - ) -> StorageResult> { - let transaction_outputs_table = self.open_table(&self.tables.transaction_outputs)?; - let mut tx_cursor = transaction_outputs_table.cursor(&self.txn)?; - let tx_current = tx_cursor.lower_bound(&event_index.0)?; - let events_table = self.open_table(&self.tables.events)?; + ) -> StorageResult> { + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + let mut tx_cursor = transaction_metadata_table.cursor(&self.txn)?; + let first_txn_location = tx_cursor.lower_bound(&event_index.0)?; + let first_relevant_transaction = match first_txn_location { + None => None, + Some((tx_index, tx_metadata)) => Some(( + tx_index, + self.file_handlers + .get_transaction_output_unchecked(tx_metadata.tx_output_location)?, + )), + }; let mut it = EventIterByEventIndex { - txn: &self.txn, - tx_current, + file_handlers: &self.file_handlers, + tx_current: first_relevant_transaction, tx_cursor, - events_table, event_index_in_tx_current: event_index.1, to_block_number, }; @@ -264,216 +328,9 @@ impl<'txn, 'env> StorageTxn<'env, RO> { } } -#[allow(missing_docs)] -/// Each [`ThinTransactionOutput`] holds a list of event contract addresses so that given a thin -/// transaction output we can get all its events from the events table (see -/// [`get_transaction_events`](crate::body::BodyStorageReader::get_transaction_events) in -/// [`BodyStorageReader`](crate::body::BodyStorageReader)). These events contract addresses are -/// taken from the events in the order of the events in [`starknet_api`][`TransactionOutput`]. -/// In particular, they are not sorted and with duplicates. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -pub enum ThinTransactionOutput { - Declare(ThinDeclareTransactionOutput), - Deploy(ThinDeployTransactionOutput), - DeployAccount(ThinDeployAccountTransactionOutput), - Invoke(ThinInvokeTransactionOutput), - L1Handler(ThinL1HandlerTransactionOutput), -} - -impl ThinTransactionOutput { - /// Returns the events contract addresses of the transaction output. - pub(crate) fn events_contract_addresses(self) -> Vec { - match self { - ThinTransactionOutput::Declare(tx_output) => tx_output.events_contract_addresses, - ThinTransactionOutput::Deploy(tx_output) => tx_output.events_contract_addresses, - ThinTransactionOutput::DeployAccount(tx_output) => tx_output.events_contract_addresses, - ThinTransactionOutput::Invoke(tx_output) => tx_output.events_contract_addresses, - ThinTransactionOutput::L1Handler(tx_output) => tx_output.events_contract_addresses, - } - } - /// Returns the events contract addresses of the transaction output. - pub(crate) fn events_contract_addresses_as_ref(&self) -> &Vec { - match self { - ThinTransactionOutput::Declare(tx_output) => &tx_output.events_contract_addresses, - ThinTransactionOutput::Deploy(tx_output) => &tx_output.events_contract_addresses, - ThinTransactionOutput::DeployAccount(tx_output) => &tx_output.events_contract_addresses, - ThinTransactionOutput::Invoke(tx_output) => &tx_output.events_contract_addresses, - ThinTransactionOutput::L1Handler(tx_output) => &tx_output.events_contract_addresses, - } - } - /// Returns the execution status. - pub fn execution_status(&self) -> &TransactionExecutionStatus { - match self { - ThinTransactionOutput::Declare(tx_output) => &tx_output.execution_status, - ThinTransactionOutput::Deploy(tx_output) => &tx_output.execution_status, - ThinTransactionOutput::DeployAccount(tx_output) => &tx_output.execution_status, - ThinTransactionOutput::Invoke(tx_output) => &tx_output.execution_status, - ThinTransactionOutput::L1Handler(tx_output) => &tx_output.execution_status, - } - } - /// Returns the actual fee. - pub fn actual_fee(&self) -> Fee { - match self { - ThinTransactionOutput::Declare(tx_output) => tx_output.actual_fee, - ThinTransactionOutput::Deploy(tx_output) => tx_output.actual_fee, - ThinTransactionOutput::DeployAccount(tx_output) => tx_output.actual_fee, - ThinTransactionOutput::Invoke(tx_output) => tx_output.actual_fee, - ThinTransactionOutput::L1Handler(tx_output) => tx_output.actual_fee, - } - } -} -/// A thin version of -/// [`InvokeTransactionOutput`](starknet_api::transaction::InvokeTransactionOutput), not holding the -/// events content. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThinInvokeTransactionOutput { - /// The actual fee paid for the transaction. - pub actual_fee: Fee, - /// The messages sent by the transaction to the base layer. - pub messages_sent: Vec, - /// The contract addresses of the events emitted by the transaction. - pub events_contract_addresses: Vec, - /// The execution status of the transaction. - pub execution_status: TransactionExecutionStatus, - /// The execution resources of the transaction. - pub execution_resources: ExecutionResources, -} - -/// A thin version of -/// [`L1HandlerTransactionOutput`](starknet_api::transaction::L1HandlerTransactionOutput), not -/// holding the events content. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThinL1HandlerTransactionOutput { - /// The actual fee paid for the transaction. - pub actual_fee: Fee, - /// The messages sent by the transaction to the base layer. - pub messages_sent: Vec, - /// The contract addresses of the events emitted by the transaction. - pub events_contract_addresses: Vec, - /// The execution status of the transaction. - pub execution_status: TransactionExecutionStatus, - /// The execution resources of the transaction. - pub execution_resources: ExecutionResources, -} - -/// A thin version of -/// [`DeclareTransactionOutput`](starknet_api::transaction::DeclareTransactionOutput), not holding -/// the events content. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThinDeclareTransactionOutput { - /// The actual fee paid for the transaction. - pub actual_fee: Fee, - /// The messages sent by the transaction to the base layer. - pub messages_sent: Vec, - /// The contract addresses of the events emitted by the transaction. - pub events_contract_addresses: Vec, - /// The execution status of the transaction. - pub execution_status: TransactionExecutionStatus, - /// The execution resources of the transaction. - pub execution_resources: ExecutionResources, -} - -/// A thin version of -/// [`DeployTransactionOutput`](starknet_api::transaction::DeployTransactionOutput), not holding the -/// events content. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThinDeployTransactionOutput { - /// The actual fee paid for the transaction. - pub actual_fee: Fee, - /// The messages sent by the transaction to the base layer. - pub messages_sent: Vec, - /// The contract addresses of the events emitted by the transaction. - pub events_contract_addresses: Vec, - /// The contract address of the deployed contract. - pub contract_address: ContractAddress, - /// The execution status of the transaction. - pub execution_status: TransactionExecutionStatus, - /// The execution resources of the transaction. - pub execution_resources: ExecutionResources, -} - -/// A thin version of -/// [`DeployAccountTransactionOutput`](starknet_api::transaction::DeployAccountTransactionOutput), -/// not holding the events content. -#[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] -pub struct ThinDeployAccountTransactionOutput { - /// The actual fee paid for the transaction. - pub actual_fee: Fee, - /// The messages sent by the transaction to the base layer. - pub messages_sent: Vec, - /// The contract addresses of the events emitted by the transaction. - pub events_contract_addresses: Vec, - /// The contract address of the deployed contract. - pub contract_address: ContractAddress, - /// The execution status of the transaction. - pub execution_status: TransactionExecutionStatus, - /// The execution resources of the transaction. - pub execution_resources: ExecutionResources, -} - -impl From for ThinTransactionOutput { - fn from(transaction_output: TransactionOutput) -> Self { - let events_contract_addresses = - transaction_output.events().iter().map(|event| event.from_address).collect(); - match transaction_output { - TransactionOutput::Declare(tx_output) => { - ThinTransactionOutput::Declare(ThinDeclareTransactionOutput { - actual_fee: tx_output.actual_fee, - messages_sent: tx_output.messages_sent, - events_contract_addresses, - execution_status: tx_output.execution_status, - execution_resources: tx_output.execution_resources, - }) - } - TransactionOutput::Deploy(tx_output) => { - ThinTransactionOutput::Deploy(ThinDeployTransactionOutput { - actual_fee: tx_output.actual_fee, - messages_sent: tx_output.messages_sent, - events_contract_addresses, - contract_address: tx_output.contract_address, - execution_status: tx_output.execution_status, - execution_resources: tx_output.execution_resources, - }) - } - TransactionOutput::DeployAccount(tx_output) => { - ThinTransactionOutput::DeployAccount(ThinDeployAccountTransactionOutput { - actual_fee: tx_output.actual_fee, - messages_sent: tx_output.messages_sent, - events_contract_addresses, - contract_address: tx_output.contract_address, - execution_status: tx_output.execution_status, - execution_resources: tx_output.execution_resources, - }) - } - TransactionOutput::Invoke(tx_output) => { - ThinTransactionOutput::Invoke(ThinInvokeTransactionOutput { - actual_fee: tx_output.actual_fee, - messages_sent: tx_output.messages_sent, - events_contract_addresses, - execution_status: tx_output.execution_status, - execution_resources: tx_output.execution_resources, - }) - } - TransactionOutput::L1Handler(tx_output) => { - ThinTransactionOutput::L1Handler(ThinL1HandlerTransactionOutput { - actual_fee: tx_output.actual_fee, - messages_sent: tx_output.messages_sent, - events_contract_addresses, - execution_status: tx_output.execution_status, - execution_resources: tx_output.execution_resources, - }) - } - } - } -} - -/// A key-value pair of the events table. -type EventsTableKeyValue = (EventsTableKey, EventContent); /// A cursor of the events table. type EventsTableCursor<'txn> = - DbCursor<'txn, RO, EventsTableKey, NoVersionValueWrapper, SimpleTable>; -/// A key-value pair of the transaction outputs table. -type TransactionOutputsKeyValue = (TransactionIndex, ThinTransactionOutput); + DbCursor<'txn, RO, EventsTableKey, NoVersionValueWrapper, SimpleTable>; /// A cursor of the transaction outputs table. -type TransactionOutputsTableCursor<'txn> = - DbCursor<'txn, RO, TransactionIndex, VersionZeroWrapper, SimpleTable>; +type TransactionMetadataTableCursor<'txn> = + DbCursor<'txn, RO, TransactionIndex, VersionZeroWrapper, SimpleTable>; diff --git a/crates/papyrus_storage/src/body/events_test.rs b/crates/papyrus_storage/src/body/events_test.rs index 9a57a27c206..aac2359bc8f 100644 --- a/crates/papyrus_storage/src/body/events_test.rs +++ b/crates/papyrus_storage/src/body/events_test.rs @@ -1,24 +1,14 @@ use std::vec; use assert_matches::assert_matches; -use camelpaste::paste; +use papyrus_test_utils::get_test_block; use pretty_assertions::assert_eq; use starknet_api::core::{ContractAddress, PatriciaKey}; use starknet_api::hash::StarkHash; use starknet_api::patricia_key; use starknet_api::transaction::{EventIndexInTransactionOutput, TransactionOffsetInBlock}; -use test_utils::get_test_block; -use crate::body::events::{ - EventIndex, - EventsReader, - ThinDeclareTransactionOutput, - ThinDeployAccountTransactionOutput, - ThinDeployTransactionOutput, - ThinInvokeTransactionOutput, - ThinL1HandlerTransactionOutput, - ThinTransactionOutput, -}; +use crate::body::events::{EventIndex, EventsReader}; use crate::body::{BodyStorageWriter, TransactionIndex}; use crate::db::table_types::Table; use crate::header::HeaderStorageWriter; @@ -189,30 +179,3 @@ async fn revert_events() { } } } - -/// macro for testing events_contract_addresses on all the variants of ThinTransactionOutput -macro_rules! test_events_contract_addresses_macro { - ($variant:ident, $variant_input:ident) => { - paste! { - #[tokio::test] - async fn []() { - let event_contract_address_1 = ContractAddress(patricia_key!("0x12")); - let event_contract_address_2 = ContractAddress(patricia_key!("0x17")); - let output = $variant_input { - events_contract_addresses: vec![event_contract_address_1, event_contract_address_2], - ..Default::default() - }; - let output = ThinTransactionOutput::$variant(output); - let res = output.events_contract_addresses(); - assert_eq!(res[0], event_contract_address_1); - assert_eq!(res[1], event_contract_address_2); - } - } - }; -} - -test_events_contract_addresses_macro!(Declare, ThinDeclareTransactionOutput); -test_events_contract_addresses_macro!(Deploy, ThinDeployTransactionOutput); -test_events_contract_addresses_macro!(DeployAccount, ThinDeployAccountTransactionOutput); -test_events_contract_addresses_macro!(Invoke, ThinInvokeTransactionOutput); -test_events_contract_addresses_macro!(L1Handler, ThinL1HandlerTransactionOutput); diff --git a/crates/papyrus_storage/src/body/mod.rs b/crates/papyrus_storage/src/body/mod.rs index a2c16481cd8..1c91f276c5f 100644 --- a/crates/papyrus_storage/src/body/mod.rs +++ b/crates/papyrus_storage/src/body/mod.rs @@ -49,8 +49,6 @@ use serde::{Deserialize, Serialize}; use starknet_api::block::{BlockBody, BlockNumber}; use starknet_api::core::ContractAddress; use starknet_api::transaction::{ - Event, - EventContent, EventIndexInTransactionOutput, Transaction, TransactionHash, @@ -59,23 +57,28 @@ use starknet_api::transaction::{ }; use tracing::debug; -use crate::body::events::{EventIndex, ThinTransactionOutput}; -use crate::db::serialization::{NoVersionValueWrapper, ValueSerde, VersionZeroWrapper}; -use crate::db::table_types::{DbCursorTrait, SimpleTable, Table}; +use crate::body::events::EventIndex; +use crate::db::serialization::{NoVersionValueWrapper, VersionZeroWrapper}; +use crate::db::table_types::{DbCursorTrait, NoValue, SimpleTable, Table}; use crate::db::{DbTransaction, TableHandle, TransactionKind, RW}; -use crate::{MarkerKind, MarkersTable, StorageError, StorageResult, StorageScope, StorageTxn}; +use crate::{ + FileHandlers, + MarkerKind, + MarkersTable, + StorageError, + StorageResult, + StorageScope, + StorageTxn, + TransactionMetadata, +}; -type TransactionsTable<'env> = - TableHandle<'env, TransactionIndex, VersionZeroWrapper, SimpleTable>; -type TransactionOutputsTable<'env> = - TableHandle<'env, TransactionIndex, VersionZeroWrapper, SimpleTable>; +type TransactionMetadataTable<'env> = + TableHandle<'env, TransactionIndex, VersionZeroWrapper, SimpleTable>; type TransactionHashToIdxTable<'env> = TableHandle<'env, TransactionHash, NoVersionValueWrapper, SimpleTable>; -type TransactionIdxToHashTable<'env> = - TableHandle<'env, TransactionIndex, NoVersionValueWrapper, SimpleTable>; type EventsTableKey = (ContractAddress, EventIndex); type EventsTable<'env> = - TableHandle<'env, EventsTableKey, NoVersionValueWrapper, SimpleTable>; + TableHandle<'env, EventsTableKey, NoVersionValueWrapper, SimpleTable>; /// The index of a transaction in a block. #[derive(Copy, Clone, Debug, Eq, PartialEq, Deserialize, Serialize, PartialOrd, Ord)] @@ -97,13 +100,7 @@ pub trait BodyStorageReader { fn get_transaction_output( &self, transaction_index: TransactionIndex, - ) -> StorageResult>; - - /// Returns the events of the transaction output at the given index. - fn get_transaction_events( - &self, - transaction_index: TransactionIndex, - ) -> StorageResult>>; + ) -> StorageResult>; /// Returns the index of the transaction with the given hash. fn get_transaction_idx_by_hash( @@ -133,7 +130,7 @@ pub trait BodyStorageReader { fn get_block_transaction_outputs( &self, block_number: BlockNumber, - ) -> StorageResult>>; + ) -> StorageResult>>; /// Returns the number of transactions in the block with the given number. fn get_block_transactions_count( @@ -142,8 +139,7 @@ pub trait BodyStorageReader { ) -> StorageResult>; } -type RevertedBlockBody = - (Vec, Vec, Vec, Vec>); +type RevertedBlockBody = (Vec, Vec, Vec); /// Interface for updating data related to the block body. pub trait BodyStorageWriter @@ -170,46 +166,32 @@ impl<'env, Mode: TransactionKind> BodyStorageReader for StorageTxn<'env, Mode> { Ok(markers_table.get(&self.txn, &MarkerKind::Body)?.unwrap_or_default()) } + // TODO(dvir): add option to get transaction with its hash. fn get_transaction( &self, transaction_index: TransactionIndex, ) -> StorageResult> { - let transactions_table = self.open_table(&self.tables.transactions)?; - let transaction = transactions_table.get(&self.txn, &transaction_index)?; - Ok(transaction) + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + let Some(tx_metadata) = transaction_metadata_table.get(&self.txn, &transaction_index)? + else { + return Ok(None); + }; + let transaction = self.file_handlers.get_transaction_unchecked(tx_metadata.tx_location)?; + Ok(Some(transaction)) } fn get_transaction_output( &self, transaction_index: TransactionIndex, - ) -> StorageResult> { - let transaction_outputs_table = self.open_table(&self.tables.transaction_outputs)?; - let transaction_output = transaction_outputs_table.get(&self.txn, &transaction_index)?; - Ok(transaction_output) - } - - fn get_transaction_events( - &self, - transaction_index: TransactionIndex, - ) -> StorageResult>> { - let tx_output = self.get_transaction_output(transaction_index)?; - let Some(tx_output) = tx_output else { + ) -> StorageResult> { + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + let Some(tx_metadata) = transaction_metadata_table.get(&self.txn, &transaction_index)? + else { return Ok(None); }; - - let events_table = self.open_table(&self.tables.events)?; - - let mut res = Vec::new(); - for (index, from_address) in tx_output.events_contract_addresses().into_iter().enumerate() { - let event_index = EventIndex(transaction_index, EventIndexInTransactionOutput(index)); - if let Some(content) = events_table.get(&self.txn, &(from_address, event_index))? { - res.push(Event { from_address, content }); - } else { - return Err(StorageError::EventNotFound { event_index, from_address }); - } - } - - Ok(Some(res)) + let transaction_output = + self.file_handlers.get_transaction_output_unchecked(tx_metadata.tx_output_location)?; + Ok(Some(transaction_output)) } fn get_transaction_idx_by_hash( @@ -226,35 +208,35 @@ impl<'env, Mode: TransactionKind> BodyStorageReader for StorageTxn<'env, Mode> { &self, tx_index: &TransactionIndex, ) -> StorageResult> { - let transaction_idx_to_hash_table = - self.open_table(&self.tables.transaction_idx_to_hash)?; - let idx = transaction_idx_to_hash_table.get(&self.txn, tx_index)?; - Ok(idx) + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + let Some(tx_metadata) = transaction_metadata_table.get(&self.txn, tx_index)? else { + return Ok(None); + }; + Ok(Some(tx_metadata.tx_hash)) } fn get_block_transactions( &self, block_number: BlockNumber, ) -> StorageResult>> { - let transactions_table = self.open_table(&self.tables.transactions)?; - self.get_transactions_in_block(block_number, transactions_table) + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + self.get_transactions_in_block(block_number, transaction_metadata_table) } fn get_block_transaction_hashes( &self, block_number: BlockNumber, ) -> StorageResult>> { - let transaction_idx_to_hash_table = - self.open_table(&self.tables.transaction_idx_to_hash)?; - self.get_transactions_in_block(block_number, transaction_idx_to_hash_table) + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + self.get_transaction_hashes_in_block(block_number, transaction_metadata_table) } fn get_block_transaction_outputs( &self, block_number: BlockNumber, - ) -> StorageResult>> { - let transaction_outputs_table = self.open_table(&self.tables.transaction_outputs)?; - self.get_transactions_in_block(block_number, transaction_outputs_table) + ) -> StorageResult>> { + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + self.get_transaction_outputs_in_block(block_number, transaction_metadata_table) } fn get_block_transactions_count( @@ -267,8 +249,8 @@ impl<'env, Mode: TransactionKind> BodyStorageReader for StorageTxn<'env, Mode> { return Ok(None); } - let transactions_table = self.open_table(&self.tables.transaction_idx_to_hash)?; - let mut cursor = transactions_table.cursor(&self.txn)?; + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; + let mut cursor = transaction_metadata_table.cursor(&self.txn)?; let Some(next_block_number) = block_number.next() else { return Ok(None); }; @@ -288,30 +270,73 @@ impl<'env, Mode: TransactionKind> BodyStorageReader for StorageTxn<'env, Mode> { } impl<'env, Mode: TransactionKind> StorageTxn<'env, Mode> { - // Helper function to get from 'table' all the values of entries with transaction index in - // 'block_number'. The returned values are ordered by the transaction offset in block in - // ascending order. - fn get_transactions_in_block( + // Returns a vector with transaction objects (can be tx hash for example). + fn get_vector_of_transaction_objects( &self, block_number: BlockNumber, - table: TableHandle<'env, TransactionIndex, V, SimpleTable>, - ) -> StorageResult>> { + transaction_metadata_table: TransactionMetadataTable<'env>, + tx_metadata_to_tx_object: fn(TransactionMetadata, &FileHandlers) -> StorageResult, + ) -> StorageResult>> { if self.get_body_marker()? <= block_number { return Ok(None); } - let mut cursor = table.cursor(&self.txn)?; + let mut cursor = transaction_metadata_table.cursor(&self.txn)?; let mut current = cursor.lower_bound(&TransactionIndex(block_number, TransactionOffsetInBlock(0)))?; + + // TODO(dvir): consider initializing with capacity based on the get_block_transactions_count + // function. let mut res = Vec::new(); - while let Some((TransactionIndex(current_block_number, _), tx)) = current { + while let Some((TransactionIndex(current_block_number, _), tx_metadata)) = current { if current_block_number != block_number { break; } - res.push(tx); + let tx_output = tx_metadata_to_tx_object(tx_metadata, &self.file_handlers)?; + res.push(tx_output); current = cursor.next()?; } Ok(Some(res)) } + + fn get_transaction_outputs_in_block( + &self, + block_number: BlockNumber, + transaction_metadata_table: TransactionMetadataTable<'env>, + ) -> StorageResult>> { + self.get_vector_of_transaction_objects( + block_number, + transaction_metadata_table, + |tx_metadata, file_handlers| { + file_handlers.get_transaction_output_unchecked(tx_metadata.tx_output_location) + }, + ) + } + + fn get_transactions_in_block( + &self, + block_number: BlockNumber, + transaction_metadata_table: TransactionMetadataTable<'env>, + ) -> StorageResult>> { + self.get_vector_of_transaction_objects( + block_number, + transaction_metadata_table, + |tx_metadata, file_handlers| { + file_handlers.get_transaction_unchecked(tx_metadata.tx_location) + }, + ) + } + + fn get_transaction_hashes_in_block( + &self, + block_number: BlockNumber, + transaction_metadata_table: TransactionMetadataTable<'env>, + ) -> StorageResult>> { + self.get_vector_of_transaction_objects( + block_number, + transaction_metadata_table, + |tx_metadata, _file_handlers| Ok(tx_metadata.tx_hash), + ) + } } impl<'env> BodyStorageWriter for StorageTxn<'env, RW> { @@ -321,26 +346,17 @@ impl<'env> BodyStorageWriter for StorageTxn<'env, RW> { update_marker(&self.txn, &markers_table, block_number)?; if self.scope != StorageScope::StateOnly { - let transactions_table = self.open_table(&self.tables.transactions)?; - let transaction_outputs_table = self.open_table(&self.tables.transaction_outputs)?; let events_table = self.open_table(&self.tables.events)?; let transaction_hash_to_idx_table = self.open_table(&self.tables.transaction_hash_to_idx)?; - let transaction_idx_to_hash_table = - self.open_table(&self.tables.transaction_idx_to_hash)?; + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; write_transactions( &block_body, &self.txn, - &transactions_table, + &self.file_handlers, &transaction_hash_to_idx_table, - &transaction_idx_to_hash_table, - block_number, - )?; - write_transaction_outputs( - block_body, - &self.txn, - &transaction_outputs_table, + &transaction_metadata_table, &events_table, block_number, )?; @@ -374,12 +390,9 @@ impl<'env> BodyStorageWriter for StorageTxn<'env, RW> { break 'reverted_block_body None; } - let transactions_table = self.open_table(&self.tables.transactions)?; - let transaction_outputs_table = self.open_table(&self.tables.transaction_outputs)?; + let transaction_metadata_table = self.open_table(&self.tables.transaction_metadata)?; let transaction_hash_to_idx_table = self.open_table(&self.tables.transaction_hash_to_idx)?; - let transaction_idx_to_hash_table = - self.open_table(&self.tables.transaction_idx_to_hash)?; let events_table = self.open_table(&self.tables.events)?; let transactions = self @@ -393,30 +406,22 @@ impl<'env> BodyStorageWriter for StorageTxn<'env, RW> { .unwrap_or_else(|| panic!("Missing transaction hashes for block {block_number}.")); // Delete the transactions data. - let mut events = vec![]; - for (offset, tx_output) in transaction_outputs.iter().enumerate() { + for (offset, (tx_hash, tx_output)) in + transaction_hashes.iter().zip(transaction_outputs.iter()).enumerate() + { let tx_index = TransactionIndex(block_number, TransactionOffsetInBlock(offset)); - let tx_hash = self.get_transaction_hash_by_idx(&tx_index)?.unwrap_or_else(|| { - panic!("Missing transaction hash for transaction index {tx_index:?}.") - }); - let mut tx_events = vec![]; - for (index, from_address) in - tx_output.events_contract_addresses_as_ref().iter().enumerate() - { - let key = - (*from_address, EventIndex(tx_index, EventIndexInTransactionOutput(index))); - tx_events.push(events_table.get(&self.txn, &key)?.unwrap_or_else(|| { - panic!("Missing events for transaction output {tx_index:?}.") - })); + + for (event_offset, event) in tx_output.events().iter().enumerate() { + let key = ( + event.from_address, + EventIndex(tx_index, EventIndexInTransactionOutput(event_offset)), + ); events_table.delete(&self.txn, &key)?; } - events.push(tx_events); - transactions_table.delete(&self.txn, &tx_index)?; - transaction_outputs_table.delete(&self.txn, &tx_index)?; - transaction_hash_to_idx_table.delete(&self.txn, &tx_hash)?; - transaction_idx_to_hash_table.delete(&self.txn, &tx_index)?; + transaction_hash_to_idx_table.delete(&self.txn, tx_hash)?; + transaction_metadata_table.delete(&self.txn, &tx_index)?; } - Some((transactions, transaction_outputs, transaction_hashes, events)) + Some((transactions, transaction_outputs, transaction_hashes)) }; markers_table.upsert(&self.txn, &MarkerKind::Body, &block_number)?; @@ -425,46 +430,34 @@ impl<'env> BodyStorageWriter for StorageTxn<'env, RW> { } } +// TODO(dvir): consider enforcing that the block_body transactions, transaction_outputs and +// transaction_hashes to be the same size. fn write_transactions<'env>( block_body: &BlockBody, txn: &DbTransaction<'env, RW>, - transactions_table: &'env TransactionsTable<'env>, + file_handlers: &FileHandlers, transaction_hash_to_idx_table: &'env TransactionHashToIdxTable<'env>, - transaction_idx_to_hash_table: &'env TransactionIdxToHashTable<'env>, + transaction_metadata_table: &'env TransactionMetadataTable<'env>, + events_table: &'env EventsTable<'env>, block_number: BlockNumber, ) -> StorageResult<()> { - for (index, (tx, tx_hash)) in - block_body.transactions.iter().zip(block_body.transaction_hashes.iter()).enumerate() + for (index, ((tx, tx_output), tx_hash)) in block_body + .transactions + .iter() + .zip(block_body.transaction_outputs.iter()) + .zip(block_body.transaction_hashes.iter()) + .enumerate() { let tx_offset_in_block = TransactionOffsetInBlock(index); let transaction_index = TransactionIndex(block_number, tx_offset_in_block); - update_tx_hash_mapping( - txn, - transaction_hash_to_idx_table, - transaction_idx_to_hash_table, - tx_hash, - transaction_index, - )?; - transactions_table.insert(txn, &transaction_index, tx)?; - } - Ok(()) -} - -fn write_transaction_outputs<'env>( - block_body: BlockBody, - txn: &DbTransaction<'env, RW>, - transaction_outputs_table: &'env TransactionOutputsTable<'env>, - events_table: &'env EventsTable<'env>, - block_number: BlockNumber, -) -> StorageResult<()> { - for (index, tx_output) in block_body.transaction_outputs.into_iter().enumerate() { - let transaction_index = TransactionIndex(block_number, TransactionOffsetInBlock(index)); - - write_events(&tx_output, txn, events_table, transaction_index)?; - transaction_outputs_table.insert( + let tx_location = file_handlers.append_transaction(tx); + let tx_output_location = file_handlers.append_transaction_output(tx_output); + write_events(tx_output, txn, events_table, transaction_index)?; + transaction_hash_to_idx_table.insert(txn, tx_hash, &transaction_index)?; + transaction_metadata_table.insert( txn, &transaction_index, - &ThinTransactionOutput::from(tx_output), + &TransactionMetadata { tx_location, tx_output_location, tx_hash: *tx_hash }, )?; } Ok(()) @@ -478,23 +471,11 @@ fn write_events<'env>( ) -> StorageResult<()> { for (index, event) in tx_output.events().iter().enumerate() { let event_index = EventIndex(transaction_index, EventIndexInTransactionOutput(index)); - events_table.insert(txn, &(event.from_address, event_index), &event.content)?; + events_table.insert(txn, &(event.from_address, event_index), &NoValue)?; } Ok(()) } -fn update_tx_hash_mapping<'env>( - txn: &DbTransaction<'env, RW>, - transaction_hash_to_idx_table: &'env TransactionHashToIdxTable<'env>, - transaction_idx_to_hash_table: &'env TransactionIdxToHashTable<'env>, - tx_hash: &TransactionHash, - transaction_index: TransactionIndex, -) -> Result<(), StorageError> { - transaction_hash_to_idx_table.insert(txn, tx_hash, &transaction_index)?; - transaction_idx_to_hash_table.insert(txn, &transaction_index, tx_hash)?; - Ok(()) -} - fn update_marker<'env>( txn: &DbTransaction<'env, RW>, markers_table: &'env MarkersTable<'env>, diff --git a/crates/papyrus_storage/src/class_test.rs b/crates/papyrus_storage/src/class_test.rs index 5ba2c19468f..72392da987c 100644 --- a/crates/papyrus_storage/src/class_test.rs +++ b/crates/papyrus_storage/src/class_test.rs @@ -1,12 +1,12 @@ use assert_matches::assert_matches; use indexmap::indexmap; +use papyrus_test_utils::read_json_file; use pretty_assertions::assert_eq; use starknet_api::block::BlockNumber; use starknet_api::core::{ClassHash, CompiledClassHash}; use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; use starknet_api::hash::StarkHash; use starknet_api::state::{ContractClass, StateNumber, ThinStateDiff}; -use test_utils::read_json_file; use super::{ClassStorageReader, ClassStorageWriter}; use crate::state::{StateStorageReader, StateStorageWriter}; diff --git a/crates/papyrus_storage/src/compiled_class_test.rs b/crates/papyrus_storage/src/compiled_class_test.rs index 6ca0439402a..f3c1229402d 100644 --- a/crates/papyrus_storage/src/compiled_class_test.rs +++ b/crates/papyrus_storage/src/compiled_class_test.rs @@ -1,8 +1,8 @@ use assert_matches::assert_matches; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use papyrus_test_utils::read_json_file; use pretty_assertions::assert_eq; use starknet_api::core::ClassHash; -use test_utils::read_json_file; use crate::compiled_class::{CasmStorageReader, CasmStorageWriter}; use crate::db::{DbError, KeyAlreadyExistsError}; diff --git a/crates/papyrus_storage/src/compression_utils_test.rs b/crates/papyrus_storage/src/compression_utils_test.rs index 7951cdcc97b..fe37f1d6411 100644 --- a/crates/papyrus_storage/src/compression_utils_test.rs +++ b/crates/papyrus_storage/src/compression_utils_test.rs @@ -1,6 +1,6 @@ +use papyrus_test_utils::read_json_file; use pretty_assertions::assert_eq; use starknet_api::deprecated_contract_class::Program; -use test_utils::read_json_file; use super::{compress, decompress, decompress_from_reader, serialize_and_compress}; use crate::db::serialization::StorageSerde; diff --git a/crates/papyrus_storage/src/db/mod.rs b/crates/papyrus_storage/src/db/mod.rs index e8314f92e73..121d4b87321 100644 --- a/crates/papyrus_storage/src/db/mod.rs +++ b/crates/papyrus_storage/src/db/mod.rs @@ -42,7 +42,7 @@ use self::table_types::{DbCursor, DbCursorTrait}; use crate::db::table_types::TableType; // Maximum number of Sub-Databases. -const MAX_DBS: usize = 20; +const MAX_DBS: usize = 18; // Note that NO_TLS mode is used by default. type EnvironmentKind = WriteMap; @@ -159,6 +159,11 @@ pub enum DbError { /// An error that occurred when trying to open a db file that does not exist. #[error("The file '{0}' does not exist.")] FileDoesNotExist(PathBuf), + // TODO(dvir): consider adding more details about the error, table name, key, value and last + // key in the tree. + /// An error that occurred when trying to append a key when it is not the last. + #[error("Append error. The key is not the last in the table.")] + Append, } type DbResult = result::Result; diff --git a/crates/papyrus_storage/src/db/serialization.rs b/crates/papyrus_storage/src/db/serialization.rs index b767646bddb..46b1fb49756 100644 --- a/crates/papyrus_storage/src/db/serialization.rs +++ b/crates/papyrus_storage/src/db/serialization.rs @@ -22,9 +22,7 @@ impl StorageSerdeEx for T { fn deserialize(bytes: &mut impl std::io::Read) -> Option { let res = Self::deserialize_from(bytes)?; - let mut buf = [0u8, 1]; - // Make sure we are at EOF. - if bytes.read(&mut buf[..]).ok()? != 0 { + if !is_all_bytes_read(bytes) { return None; } Some(res) @@ -97,9 +95,7 @@ impl ValueSerde for VersionZeroWrapper { } let res = Self::Value::deserialize_from(bytes)?; - let mut buf = [0u8, 1]; - // Make sure we are at EOF. - if bytes.read(&mut buf[..]).ok()? != 0 { + if !is_all_bytes_read(bytes) { return None; } Some(res) @@ -147,16 +143,14 @@ impl ValueSerde ); return None; } - if version[0] < VERSION { + let res = if version[0] < VERSION { debug!("Migrating value with version {} to version {}", version[0], VERSION); - return T::try_from_older_version(bytes, version[0]).ok(); - } - let res = Self::Value::deserialize_from(bytes)?; + T::try_from_older_version(bytes, version[0]).ok()? + } else { + Self::Value::deserialize_from(bytes)? + }; - let mut buf = [0u8, 1]; - // Make sure we are at EOF. - if bytes.read(&mut buf[..]).ok()? != 0 { - // TODO: Return an error here. + if !is_all_bytes_read(bytes) { return None; } Some(res) @@ -174,3 +168,10 @@ pub enum StorageSerdeError { #[error("Failed to migrate value")] Migration, } + +// Make sure we are at EOF. +fn is_all_bytes_read(bytes: &mut impl std::io::Read) -> bool { + let mut buf = [0u8, 1]; + // TODO: return an error instead of false. + bytes.read(&mut buf[..]).ok() == Some(0) +} diff --git a/crates/papyrus_storage/src/db/table_types/dup_sort_tables.rs b/crates/papyrus_storage/src/db/table_types/dup_sort_tables.rs index 50076e6c11e..6d49b3c4205 100644 --- a/crates/papyrus_storage/src/db/table_types/dup_sort_tables.rs +++ b/crates/papyrus_storage/src/db/table_types/dup_sort_tables.rs @@ -274,6 +274,61 @@ impl<'env, K: KeyTrait + Debug, V: ValueSerde + Debug, T: DupSortTableType + Dup Ok(()) } + // TODO(dvir): consider first checking if the key is equal to the last key, delete the last key, + // and then append instead of optimistically append. + fn append( + &'env self, + txn: &DbTransaction<'env, RW>, + key: &K, + value: &::Value, + ) -> DbResult<()> { + let main_key = T::get_main_key(key)?; + let sub_key_and_value = T::get_sub_key_and_value(key, value)?; + + let mut cursor = txn.txn.cursor(&self.database)?; + match cursor.put(&main_key, &sub_key_and_value, WriteFlags::APPEND_DUP | WriteFlags::APPEND) + { + Err(libmdbx::Error::KeyMismatch) => { + // This case can happen if the appended sub_key_and_value is smaller than the last + // entry value, but the sub key itself is equal. + // For example: append (0,0) -> 1, old last entry: (0,0) -> 2. + let (last_main_key_bytes, last_key_suffix_and_value_bytes) = + cursor.last::, DbValueType<'_>>()?.expect( + "Should have a last key. otherwise the previous put operation would \ + succeed.", + ); + + // If the appended key is equal to the last key in the table, we can append it. To + // do that we first need to delete the old entry. + if last_main_key_bytes == main_key.as_slice() + && last_key_suffix_and_value_bytes.starts_with(&T::get_sub_key(key)?) + { + cursor.del(WriteFlags::empty())?; + cursor.put( + &main_key, + &sub_key_and_value, + WriteFlags::APPEND_DUP | WriteFlags::APPEND, + )?; + + Ok(()) + } else { + Err(DbError::Append) + } + } + Ok(()) => { + // In the case of overriding the last key with a bigger value, we need to delete the + // old entry. + if let Some(prev) = cursor.prev_dup::, DbValueType<'_>>()? { + if prev.1.starts_with(&T::get_sub_key(key)?) { + cursor.del(WriteFlags::empty())?; + } + } + Ok(()) + } + Err(err) => Err(err.into()), + } + } + fn delete(&'env self, txn: &DbTransaction<'env, RW>, key: &Self::Key) -> DbResult<()> { let main_key = T::get_main_key(key)?; let first_sub_key = T::get_sub_key_lower_bound(key)?; @@ -291,6 +346,47 @@ impl<'env, K: KeyTrait + Debug, V: ValueSerde + Debug, T: DupSortTableType + Dup } } +// TODO(dvir): consider adding unchecked version of the append function. +#[allow(private_bounds)] +impl<'env, K: KeyTrait + Debug, V: ValueSerde + Debug, T: DupSortTableType + DupSortUtils> + TableHandle<'env, K, V, T> +{ + // Append a new value to the given key. The sub key must be bigger than the last sub key for the + // given main key, otherwise an error will be returned. + // In contrast to the append function in the Table trait, this function will return an error if + // The sub key is equal to the last sub key of the given main key. + #[allow(dead_code)] + pub(crate) fn append_greater_sub_key( + &'env self, + txn: &DbTransaction<'env, RW>, + key: &K, + value: &::Value, + ) -> DbResult<()> { + let main_key = T::get_main_key(key)?; + let sub_key_and_value = T::get_sub_key_and_value(key, value)?; + + let mut cursor = txn.txn.cursor(&self.database)?; + cursor.put(&main_key, &sub_key_and_value, WriteFlags::APPEND_DUP).map_err( + |err| match err { + libmdbx::Error::KeyMismatch => DbError::Append, + _ => err.into(), + }, + )?; + + // This checks the case where the the sub key is already the last in the sub tree; in this + // case, we revert the last put and return an error. + if let Some(prev) = cursor.prev_dup::, DbValueType<'_>>()? { + if prev.1.starts_with(&T::get_sub_key(key)?) { + cursor.next_dup::, DbValueType<'_>>()?; + cursor.del(WriteFlags::empty())?; + return Err(DbError::Append); + } + } + + Ok(()) + } +} + impl< 'txn, Mode: TransactionKind, diff --git a/crates/papyrus_storage/src/db/table_types/dup_sort_tables_test.rs b/crates/papyrus_storage/src/db/table_types/dup_sort_tables_test.rs index 255ddbbb81d..c8c01a9275e 100644 --- a/crates/papyrus_storage/src/db/table_types/dup_sort_tables_test.rs +++ b/crates/papyrus_storage/src/db/table_types/dup_sort_tables_test.rs @@ -1,7 +1,12 @@ +use assert_matches::assert_matches; + +use super::{DupSortTableType, DupSortUtils}; use crate::db::db_test::get_test_env; +use crate::db::serialization::NoVersionValueWrapper; use crate::db::table_types::dup_sort_tables::add_one; -use crate::db::table_types::test_utils::{random_table_test, table_test}; -use crate::db::DbWriter; +use crate::db::table_types::test_utils::{random_table_test, table_test, TableKey, TableValue}; +use crate::db::table_types::Table; +use crate::db::{DbError, DbResult, DbWriter, TableIdentifier}; #[test] fn common_prefix_table() { @@ -18,6 +23,59 @@ fn common_prefix_compare_with_simple_table_random() { random_table_test(simple_table, common_prefix_table, &reader, &mut writer); } +#[test] +fn common_prefix_append_greater_sub_key() { + append_greater_sub_key_test(DbWriter::create_common_prefix_table); +} + +#[allow(clippy::type_complexity)] +fn append_greater_sub_key_test( + create_table: fn( + &mut DbWriter, + &'static str, + ) -> DbResult>, +) where + T: DupSortTableType + DupSortUtils<(u32, u32), NoVersionValueWrapper>, +{ + let ((_reader, mut writer), _temp_dir) = get_test_env(); + let table_id = create_table(&mut writer, "table").unwrap(); + + let txn = writer.begin_rw_txn().unwrap(); + + let handle = txn.open_table(&table_id).unwrap(); + handle.append_greater_sub_key(&txn, &(2, 2), &22).unwrap(); + handle.append_greater_sub_key(&txn, &(2, 3), &23).unwrap(); + handle.append_greater_sub_key(&txn, &(1, 1), &11).unwrap(); + handle.append_greater_sub_key(&txn, &(3, 0), &30).unwrap(); + + // For DupSort tables append with key that already exists should fail. Try append with smaller + // bigger and equal values. + let result = handle.append_greater_sub_key(&txn, &(2, 2), &0); + assert_matches!(result, Err(DbError::Append)); + + let result = handle.append_greater_sub_key(&txn, &(2, 2), &22); + assert_matches!(result, Err(DbError::Append)); + + let result = handle.append_greater_sub_key(&txn, &(2, 2), &100); + assert_matches!(result, Err(DbError::Append)); + + // As before, but for the last main key. + let result = handle.append_greater_sub_key(&txn, &(3, 0), &0); + assert_matches!(result, Err(DbError::Append)); + + let result = handle.append_greater_sub_key(&txn, &(3, 0), &30); + assert_matches!(result, Err(DbError::Append)); + + let result = handle.append_greater_sub_key(&txn, &(3, 0), &100); + assert_matches!(result, Err(DbError::Append)); + + // Check the final database. + assert_eq!(handle.get(&txn, &(2, 2)).unwrap(), Some(22)); + assert_eq!(handle.get(&txn, &(2, 3)).unwrap(), Some(23)); + assert_eq!(handle.get(&txn, &(1, 1)).unwrap(), Some(11)); + assert_eq!(handle.get(&txn, &(3, 0)).unwrap(), Some(30)); +} + #[test] fn add_one_test() { let mut bytes; diff --git a/crates/papyrus_storage/src/db/table_types/mod.rs b/crates/papyrus_storage/src/db/table_types/mod.rs index 6563bd725e4..28497afd9db 100644 --- a/crates/papyrus_storage/src/db/table_types/mod.rs +++ b/crates/papyrus_storage/src/db/table_types/mod.rs @@ -14,6 +14,9 @@ pub(crate) use simple_table::SimpleTable; #[cfg(test)] pub(crate) mod test_utils; +// TODO(dvir): consider adding the create_table method to the Table trait. +// TODO(dvir): add some documentation to the Table and the cursor traits. +// TODO(dvir): consider adding unchecked version of the those functions. pub(crate) trait Table<'env> { type Key: KeyTrait + Debug; type Value: ValueSerde + Debug; @@ -46,9 +49,21 @@ pub(crate) trait Table<'env> { value: &::Value, ) -> DbResult<()>; + // Append a key value pair to the end of the table. The key must be bigger than or equal to + // the last key in the table; otherwise, an error will be returned. + #[allow(dead_code)] + fn append( + &'env self, + txn: &DbTransaction<'env, RW>, + key: &Self::Key, + value: &::Value, + ) -> DbResult<()>; + fn delete(&'env self, txn: &DbTransaction<'env, RW>, key: &Self::Key) -> DbResult<()>; } +// TODO(dvir): consider adding append functionality using a cursor. It should be more efficient for +// more than a single append operation (also for other table types). pub(crate) trait DbCursorTrait { type Key: KeyTrait + Debug; type Value: ValueSerde + Debug; @@ -72,3 +87,7 @@ pub(crate) struct DbCursor<'txn, Mode: TransactionKind, K: KeyTrait, V: ValueSer } pub(crate) trait TableType {} + +// A value place holder for tables where we don't need a value. +#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)] +pub(crate) struct NoValue; diff --git a/crates/papyrus_storage/src/db/table_types/simple_table.rs b/crates/papyrus_storage/src/db/table_types/simple_table.rs index 9c464cbb0aa..98571e539bc 100644 --- a/crates/papyrus_storage/src/db/table_types/simple_table.rs +++ b/crates/papyrus_storage/src/db/table_types/simple_table.rs @@ -113,6 +113,24 @@ impl<'env, K: KeyTrait + Debug, V: ValueSerde + Debug> Table<'env> Ok(()) } + #[allow(dead_code)] + fn append( + &'env self, + txn: &DbTransaction<'env, RW>, + key: &K, + value: &::Value, + ) -> DbResult<()> { + let data = V::serialize(value)?; + let bin_key = key.serialize()?; + txn.txn.put(&self.database, bin_key, data, WriteFlags::APPEND).map_err( + |err| match err { + libmdbx::Error::KeyMismatch => DbError::Append, + _ => err.into(), + }, + )?; + Ok(()) + } + fn delete(&'env self, txn: &DbTransaction<'env, RW>, key: &Self::Key) -> DbResult<()> { let bin_key = key.serialize()?; txn.txn.del(&self.database, bin_key, None)?; diff --git a/crates/papyrus_storage/src/db/table_types/test_utils.rs b/crates/papyrus_storage/src/db/table_types/test_utils.rs index d2b52e2b87f..579f405b973 100644 --- a/crates/papyrus_storage/src/db/table_types/test_utils.rs +++ b/crates/papyrus_storage/src/db/table_types/test_utils.rs @@ -1,3 +1,4 @@ +use assert_matches::assert_matches; use rand::rngs::ThreadRng; use rand::Rng; use tracing::debug; @@ -43,6 +44,9 @@ pub(crate) fn table_test( let upsert_test_table = create_table(&mut writer, "upsert_test").unwrap(); upsert_test(upsert_test_table, &mut writer); + let append_test_table = create_table(&mut writer, "append_test").unwrap(); + append_test(append_test_table, &mut writer); + let delete_test_table = create_table(&mut writer, "delete_test").unwrap(); delete_test(delete_test_table, &mut writer); @@ -121,6 +125,51 @@ fn upsert_test( assert_eq!(table.get(&txn, &(1, 1)).unwrap(), Some(0)); } +fn append_test( + table_id: TableIdentifier, + writer: &mut DbWriter, +) where + for<'env> TableHandle<'env, TableKey, TableValue, T>: + Table<'env, Key = TableKey, Value = TableValue>, +{ + let txn = writer.begin_rw_txn().unwrap(); + let table = txn.open_table(&table_id).unwrap(); + + // Append to an empty table. + table.append(&txn, &(1, 1), &11).unwrap(); + assert_eq!(table.get(&txn, &(1, 1)).unwrap(), Some(11)); + + // Successful appends. + table.append(&txn, &(1, 1), &0).unwrap(); + table.append(&txn, &(1, 2), &12).unwrap(); + table.append(&txn, &(2, 0), &20).unwrap(); + table.append(&txn, &(2, 2), &22).unwrap(); + + assert_eq!(table.get(&txn, &(1, 1)).unwrap(), Some(0)); + assert_eq!(table.get(&txn, &(1, 2)).unwrap(), Some(12)); + assert_eq!(table.get(&txn, &(2, 0)).unwrap(), Some(20)); + assert_eq!(table.get(&txn, &(2, 2)).unwrap(), Some(22)); + + // Override the last key with a smaller value. + table.append(&txn, &(2, 2), &0).unwrap(); + assert_eq!(table.get(&txn, &(2, 2)).unwrap(), Some(0)); + + // Override the last key with a bigger value. + table.append(&txn, &(2, 2), &100).unwrap(); + assert_eq!(table.get(&txn, &(2, 2)).unwrap(), Some(100)); + + // Append key that is not the last, should fail. + assert_matches!(table.append(&txn, &(0, 0), &0), Err(DbError::Append)); + assert_matches!(table.append(&txn, &(1, 3), &0), Err(DbError::Append)); + assert_matches!(table.append(&txn, &(2, 1), &0), Err(DbError::Append)); + + // Check the final database. + assert_eq!(table.get(&txn, &(1, 1)).unwrap(), Some(0)); + assert_eq!(table.get(&txn, &(1, 2)).unwrap(), Some(12)); + assert_eq!(table.get(&txn, &(2, 0)).unwrap(), Some(20)); + assert_eq!(table.get(&txn, &(2, 2)).unwrap(), Some(100)); +} + fn delete_test( table_id: TableIdentifier, writer: &mut DbWriter, @@ -261,8 +310,7 @@ pub(crate) fn random_table_test( for iter in 0..ITERS { debug!("iteration: {iter:?}"); let wtxn = writer.begin_rw_txn().unwrap(); - // TODO(dvir): add append functionality to this test. - let random_op = rng.gen_range(0..3); + let random_op = rng.gen_range(0..4); let key = get_random_key(&mut rng); let value = rng.gen_range(0..MAX_VALUE); @@ -283,6 +331,18 @@ pub(crate) fn random_table_test( first_table.upsert(&wtxn, &key, &value).unwrap(); second_table.upsert(&wtxn, &key, &value).unwrap(); } else if random_op == 2 { + // Append + // TODO(dvir): consider increasing the number of successful appends (append of not the + // last entry will fail). + debug!("append: {key:?}, {value:?}"); + let first_res = first_table.append(&wtxn, &key, &value); + let second_res = second_table.append(&wtxn, &key, &value); + assert!( + (first_res.is_ok() && second_res.is_ok()) + || (matches!(first_res.unwrap_err(), DbError::Append) + && matches!(second_res.unwrap_err(), DbError::Append)) + ); + } else if random_op == 3 { // Delete debug!("delete: {key:?}"); first_table.delete(&wtxn, &key).unwrap(); diff --git a/crates/papyrus_storage/src/deprecated/migrations_test.rs b/crates/papyrus_storage/src/deprecated/migrations_test.rs index 6a25a28f2d9..7e3548d1d9b 100644 --- a/crates/papyrus_storage/src/deprecated/migrations_test.rs +++ b/crates/papyrus_storage/src/deprecated/migrations_test.rs @@ -1,11 +1,11 @@ use std::fmt::Debug; +use papyrus_test_utils::{get_rng, GetTestInstance}; use starknet_api::block::{BlockNumber, StarknetVersion}; use starknet_api::core::{EventCommitment, TransactionCommitment}; use starknet_api::hash::StarkFelt; use starknet_api::stark_felt; use test_log::test; -use test_utils::{get_rng, GetTestInstance}; use crate::db::serialization::VersionZeroWrapper; use crate::db::table_types::Table; diff --git a/crates/papyrus_storage/src/deprecated/test_instances.rs b/crates/papyrus_storage/src/deprecated/test_instances.rs index ed40ad356b5..d14a061cb2c 100644 --- a/crates/papyrus_storage/src/deprecated/test_instances.rs +++ b/crates/papyrus_storage/src/deprecated/test_instances.rs @@ -1,3 +1,4 @@ +use papyrus_test_utils::{auto_impl_get_test_instance, GetTestInstance}; use starknet_api::block::{BlockHash, BlockNumber, BlockTimestamp, GasPricePerToken}; use starknet_api::core::{ EventCommitment, @@ -7,7 +8,6 @@ use starknet_api::core::{ TransactionCommitment, }; use starknet_api::data_availability::L1DataAvailabilityMode; -use test_utils::{auto_impl_get_test_instance, GetTestInstance}; use crate::deprecated::migrations::{StorageBlockHeaderV0, StorageBlockHeaderV1}; diff --git a/crates/papyrus_storage/src/lib.rs b/crates/papyrus_storage/src/lib.rs index 5f08f30fd43..6bce7dee8b0 100644 --- a/crates/papyrus_storage/src/lib.rs +++ b/crates/papyrus_storage/src/lib.rs @@ -114,7 +114,7 @@ use db::serialization::{ VersionWrapper, VersionZeroWrapper, }; -use db::table_types::Table; +use db::table_types::{NoValue, Table}; use mmap_file::{ open_file, FileHandler, @@ -133,12 +133,11 @@ use starknet_api::core::{ClassHash, ContractAddress, Nonce}; use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContractClass; use starknet_api::hash::StarkFelt; use starknet_api::state::{ContractClass, StateNumber, StorageKey, ThinStateDiff}; -use starknet_api::transaction::{EventContent, Transaction, TransactionHash}; +use starknet_api::transaction::{Transaction, TransactionHash, TransactionOutput}; use tracing::{debug, warn}; use validator::Validate; use version::{StorageVersionError, Version}; -use crate::body::events::ThinTransactionOutput; use crate::body::TransactionIndex; use crate::db::table_types::SimpleTable; use crate::db::{ @@ -188,9 +187,7 @@ pub fn open_storage( file_offsets: db_writer.create_simple_table("file_offsets")?, state_diffs: db_writer.create_simple_table("state_diffs")?, transaction_hash_to_idx: db_writer.create_simple_table("transaction_hash_to_idx")?, - transaction_idx_to_hash: db_writer.create_simple_table("transaction_idx_to_hash")?, - transaction_outputs: db_writer.create_simple_table("transaction_outputs")?, - transactions: db_writer.create_simple_table("transactions")?, + transaction_metadata: db_writer.create_simple_table("transaction_metadata")?, // Version tables starknet_version: db_writer.create_simple_table("starknet_version")?, @@ -498,9 +495,7 @@ impl<'env, Mode: TransactionKind> StorageTxn<'env, Mode> { let unused_tables = [ self.tables.events.name, self.tables.transaction_hash_to_idx.name, - self.tables.transaction_idx_to_hash.name, - self.tables.transaction_outputs.name, - self.tables.transactions.name, + self.tables.transaction_metadata.name, ]; if unused_tables.contains(&table_id.name) { return Err(StorageError::ScopeError { @@ -528,16 +523,15 @@ struct_field_names! { declared_classes_block: TableIdentifier, SimpleTable>, deprecated_declared_classes: TableIdentifier, SimpleTable>, deployed_contracts: TableIdentifier<(ContractAddress, BlockNumber), VersionZeroWrapper, SimpleTable>, - events: TableIdentifier<(ContractAddress, EventIndex), NoVersionValueWrapper, SimpleTable>, + events: TableIdentifier<(ContractAddress, EventIndex), NoVersionValueWrapper, SimpleTable>, headers: TableIdentifier, SimpleTable>, markers: TableIdentifier, SimpleTable>, nonces: TableIdentifier<(ContractAddress, BlockNumber), VersionZeroWrapper, SimpleTable>, file_offsets: TableIdentifier, SimpleTable>, state_diffs: TableIdentifier, SimpleTable>, transaction_hash_to_idx: TableIdentifier, SimpleTable>, - transaction_idx_to_hash: TableIdentifier, SimpleTable>, - transaction_outputs: TableIdentifier, SimpleTable>, - transactions: TableIdentifier, SimpleTable>, + // TODO(dvir): consider not saving transaction hash and calculating it from the transaction on demand. + transaction_metadata: TableIdentifier, SimpleTable>, // Version tables starknet_version: TableIdentifier, SimpleTable>, @@ -561,6 +555,13 @@ macro_rules! struct_field_names { } use struct_field_names; +#[derive(Clone, Debug, PartialEq, Eq)] +pub(crate) struct TransactionMetadata { + tx_hash: TransactionHash, + tx_location: LocationInFile, + tx_output_location: LocationInFile, +} + // TODO: sort the variants alphabetically. /// Error type for the storage crate. #[allow(missing_docs)] @@ -670,6 +671,8 @@ struct FileHandlers { contract_class: FileHandler, Mode>, casm: FileHandler, Mode>, deprecated_contract_class: FileHandler, Mode>, + transaction_output: FileHandler, Mode>, + transaction: FileHandler, Mode>, } impl FileHandlers { @@ -704,6 +707,16 @@ impl FileHandlers { self.casm.flush(); self.deprecated_contract_class.flush(); } + + // Appends a thin transaction output to the corresponding file and returns its location. + fn append_transaction_output(&self, transaction_output: &TransactionOutput) -> LocationInFile { + self.clone().transaction_output.append(transaction_output) + } + + // Appends a transaction to the corresponding file and returns its location. + fn append_transaction(&self, transaction: &Transaction) -> LocationInFile { + self.clone().transaction.append(transaction) + } } impl FileHandlers { @@ -714,6 +727,8 @@ impl FileHandlers { ("contract_class".to_string(), self.contract_class.stats()), ("casm".to_string(), self.casm.stats()), ("deprecated_contract_class".to_string(), self.deprecated_contract_class.stats()), + ("transaction_output".to_string(), self.transaction_output.stats()), + ("transaction".to_string(), self.transaction.stats()), ]) } @@ -754,6 +769,24 @@ impl FileHandlers { msg: format!("DeprecatedContractClass at location {:?} not found.", location), }) } + + // Returns the transaction output at the given location or an error in case it doesn't + // exist. + fn get_transaction_output_unchecked( + &self, + location: LocationInFile, + ) -> StorageResult { + self.transaction_output.get(location)?.ok_or(StorageError::DBInconsistency { + msg: format!("TransactionOutput at location {:?} not found.", location), + }) + } + + // Returns the transaction at the given location or an error in case it doesn't exist. + fn get_transaction_unchecked(&self, location: LocationInFile) -> StorageResult { + self.transaction.get(location)?.ok_or(StorageError::DBInconsistency { + msg: format!("Transaction at location {:?} not found.", location), + }) + } } fn open_storage_files( @@ -765,6 +798,7 @@ fn open_storage_files( let db_transaction = db_reader.begin_ro_txn()?; let table = db_transaction.open_table(file_offsets_table)?; + // TODO(dvir): consider using a loop here to avoid code duplication. let thin_state_diff_offset = table.get(&db_transaction, &OffsetKind::ThinStateDiff)?.unwrap_or_default(); let (thin_state_diff_writer, thin_state_diff_reader) = open_file( @@ -788,23 +822,40 @@ fn open_storage_files( let deprecated_contract_class_offset = table.get(&db_transaction, &OffsetKind::DeprecatedContractClass)?.unwrap_or_default(); let (deprecated_contract_class_writer, deprecated_contract_class_reader) = open_file( - mmap_file_config, + mmap_file_config.clone(), db_config.path().join("deprecated_contract_class.dat"), deprecated_contract_class_offset, )?; + let transaction_output_offset = + table.get(&db_transaction, &OffsetKind::TransactionOutput)?.unwrap_or_default(); + let (transaction_output_writer, transaction_output_reader) = open_file( + mmap_file_config.clone(), + db_config.path().join("transaction_output.dat"), + transaction_output_offset, + )?; + + let transaction_offset = + table.get(&db_transaction, &OffsetKind::Transaction)?.unwrap_or_default(); + let (transaction_writer, transaction_reader) = + open_file(mmap_file_config, db_config.path().join("transaction.dat"), transaction_offset)?; + Ok(( FileHandlers { thin_state_diff: thin_state_diff_writer, contract_class: contract_class_writer, casm: casm_writer, deprecated_contract_class: deprecated_contract_class_writer, + transaction_output: transaction_output_writer, + transaction: transaction_writer, }, FileHandlers { thin_state_diff: thin_state_diff_reader, contract_class: contract_class_reader, casm: casm_reader, deprecated_contract_class: deprecated_contract_class_reader, + transaction_output: transaction_output_reader, + transaction: transaction_reader, }, )) } @@ -820,6 +871,10 @@ pub enum OffsetKind { Casm, /// A deprecated contract class file. DeprecatedContractClass, + /// A transaction output file. + TransactionOutput, + /// A transaction file. + Transaction, } /// A storage query. Used for benchmarking in the storage_benchmark binary. diff --git a/crates/papyrus_storage/src/mmap_file/mmap_file_test.rs b/crates/papyrus_storage/src/mmap_file/mmap_file_test.rs index 5325eb28160..01cc34f5fcc 100644 --- a/crates/papyrus_storage/src/mmap_file/mmap_file_test.rs +++ b/crates/papyrus_storage/src/mmap_file/mmap_file_test.rs @@ -1,5 +1,6 @@ use std::sync::Arc; +use papyrus_test_utils::get_rng; use pretty_assertions::assert_eq; use rand::Rng; use tempfile::tempdir; @@ -272,3 +273,32 @@ fn reader_when_writer_is_out_of_scope() { dir.close().unwrap(); } + +#[test] +fn storage_serde_test_location_in_file() { + let item = LocationInFile::get_test_instance(&mut get_rng()); + let mut buf = Vec::new(); + item.serialize_into(&mut buf).unwrap(); + let res = LocationInFile::deserialize_from(&mut buf.as_slice()).unwrap(); + assert_eq!(res, item); +} + +#[test] +fn location_in_file_serialization_max_value() { + let item = LocationInFile { offset: (1 << 48) - 1, len: (1 << 32) - 1 }; + let mut buf = Vec::new(); + item.serialize_into(&mut buf).unwrap(); + let res = LocationInFile::deserialize_from(&mut buf.as_slice()).unwrap(); + assert_eq!(res, item); +} + +#[test] +fn location_in_file_serialization_order() { + let location_1 = LocationInFile { offset: 1, len: 1 }; + let location_256 = LocationInFile { offset: 256, len: 1 }; + let mut serialized_1 = Vec::new(); + location_1.serialize_into(&mut serialized_1).unwrap(); + let mut serialized_256 = Vec::new(); + location_256.serialize_into(&mut serialized_256).unwrap(); + assert!(serialized_256 > serialized_1); +} diff --git a/crates/papyrus_storage/src/mmap_file/mod.rs b/crates/papyrus_storage/src/mmap_file/mod.rs index 800d7d7e0db..a3908ae1164 100644 --- a/crates/papyrus_storage/src/mmap_file/mod.rs +++ b/crates/papyrus_storage/src/mmap_file/mod.rs @@ -19,15 +19,15 @@ use memmap2::{MmapMut, MmapOptions}; use papyrus_config::dumping::{ser_param, SerializeConfig}; use papyrus_config::{ParamPath, ParamPrivacyInput, SerializedParam}; #[cfg(test)] +use papyrus_test_utils::GetTestInstance; +#[cfg(test)] use rand_chacha::ChaCha8Rng; use serde::{Deserialize, Serialize}; -#[cfg(test)] -use test_utils::GetTestInstance; use thiserror::Error; use tracing::{debug, instrument, trace}; use validator::{Validate, ValidationError}; -use crate::db::serialization::{StorageSerde, ValueSerde}; +use crate::db::serialization::{StorageSerde, StorageSerdeError, ValueSerde}; use crate::db::{TransactionKind, RO, RW}; type MmapFileResult = result::Result; @@ -285,20 +285,24 @@ impl FileHandler { } } -// TODO(dan): use varint serialization. +// This serialization writes the offset as 6 bytes and the length as 4 bytes. +// This means the size of the file is limited to 256TB (1<<48 bits) and the length of the data +// is limited to 4GB (1<<32 bits). impl StorageSerde for LocationInFile { - fn serialize_into( - &self, - res: &mut impl std::io::Write, - ) -> Result<(), crate::db::serialization::StorageSerdeError> { - self.offset.serialize_into(res)?; - self.len.serialize_into(res)?; + fn serialize_into(&self, res: &mut impl std::io::Write) -> Result<(), StorageSerdeError> { + let bytes = &self.len.to_be_bytes(); + res.write_all(&bytes[4..])?; + let bytes = &self.offset.to_be_bytes(); + res.write_all(&bytes[2..])?; Ok(()) } fn deserialize_from(bytes: &mut impl std::io::Read) -> Option { - let offset = usize::deserialize_from(bytes)?; - let len = usize::deserialize_from(bytes)?; + let mut arr = [0u8; 8]; + bytes.read_exact(&mut arr[4..]).ok()?; + let len = usize::from_be_bytes(arr); + bytes.read_exact(&mut arr[2..]).ok()?; + let offset = usize::from_be_bytes(arr); Some(Self { offset, len }) } } diff --git a/crates/papyrus_storage/src/serialization/serializers.rs b/crates/papyrus_storage/src/serialization/serializers.rs index 5022fdd38c8..a56de4d2f6d 100644 --- a/crates/papyrus_storage/src/serialization/serializers.rs +++ b/crates/papyrus_storage/src/serialization/serializers.rs @@ -79,13 +79,17 @@ use starknet_api::transaction::{ Calldata, ContractAddressSalt, DeclareTransaction, + DeclareTransactionOutput, DeclareTransactionV0V1, DeclareTransactionV2, DeclareTransactionV3, DeployAccountTransaction, + DeployAccountTransactionOutput, DeployAccountTransactionV1, DeployAccountTransactionV3, DeployTransaction, + DeployTransactionOutput, + Event, EventContent, EventData, EventIndexInTransactionOutput, @@ -93,10 +97,12 @@ use starknet_api::transaction::{ ExecutionResources, Fee, InvokeTransaction, + InvokeTransactionOutput, InvokeTransactionV0, InvokeTransactionV1, InvokeTransactionV3, L1HandlerTransaction, + L1HandlerTransactionOutput, L1ToL2Payload, L2ToL1Payload, MessageToL1, @@ -111,19 +117,12 @@ use starknet_api::transaction::{ TransactionExecutionStatus, TransactionHash, TransactionOffsetInBlock, + TransactionOutput, TransactionSignature, TransactionVersion, }; -use crate::body::events::{ - EventIndex, - ThinDeclareTransactionOutput, - ThinDeployAccountTransactionOutput, - ThinDeployTransactionOutput, - ThinInvokeTransactionOutput, - ThinL1HandlerTransactionOutput, - ThinTransactionOutput, -}; +use crate::body::events::EventIndex; use crate::body::TransactionIndex; use crate::compression_utils::{ compress, @@ -133,13 +132,14 @@ use crate::compression_utils::{ IsCompressed, }; use crate::db::serialization::{StorageSerde, StorageSerdeError}; +use crate::db::table_types::NoValue; use crate::header::StorageBlockHeader; use crate::mmap_file::LocationInFile; #[cfg(test)] use crate::serialization::serializers_test::{create_storage_serde_test, StorageSerdeTest}; use crate::state::data::IndexedDeprecatedContractClass; use crate::version::Version; -use crate::{MarkerKind, OffsetKind}; +use crate::{MarkerKind, OffsetKind, TransactionMetadata}; // The threshold for compressing transactions. const COMPRESSION_THRESHOLD_BYTES: usize = 384; @@ -165,7 +165,6 @@ auto_storage_serde! { pub n_transactions: Option, pub n_events: Option, } - pub struct BlockNumber(pub u64); pub struct BlockSignature(pub Signature); pub enum BlockStatus { Pending = 0, @@ -256,6 +255,10 @@ auto_storage_serde! { pub name: String, pub r#type: EventType, } + pub struct Event { + pub from_address: ContractAddress, + pub content: EventContent, + } pub struct EventContent { pub keys: Vec, pub data: EventData, @@ -326,6 +329,8 @@ auto_storage_serde! { ContractClass = 1, Casm = 2, DeprecatedContractClass = 3, + TransactionOutput = 4, + Transaction = 5, } pub struct PaymasterData(pub Vec); pub struct PoseidonHash(pub StarkFelt); @@ -373,54 +378,15 @@ auto_storage_serde! { pub struct StarknetVersion(pub String); pub struct StateDiffCommitment(pub PoseidonHash); pub struct Tip(pub u64); - pub struct ThinDeclareTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinDeployTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub contract_address: ContractAddress, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinDeployAccountTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub contract_address: ContractAddress, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } pub struct TransactionCommitment(pub StarkHash); pub struct TypedParameter { pub name: String, pub r#type: String, } - pub struct ThinInvokeTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinL1HandlerTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub enum ThinTransactionOutput { - Declare(ThinDeclareTransactionOutput) = 0, - Deploy(ThinDeployTransactionOutput) = 1, - DeployAccount(ThinDeployAccountTransactionOutput) = 2, - Invoke(ThinInvokeTransactionOutput) = 3, - L1Handler(ThinL1HandlerTransactionOutput) = 4, + pub struct TransactionMetadata { + pub tx_hash: TransactionHash, + pub tx_location: LocationInFile, + pub tx_output_location: LocationInFile, } pub enum Transaction { Declare(DeclareTransaction) = 0, @@ -438,7 +404,13 @@ auto_storage_serde! { } pub struct TransactionHash(pub StarkHash); struct TransactionIndex(pub BlockNumber, pub TransactionOffsetInBlock); - pub struct TransactionOffsetInBlock(pub usize); + pub enum TransactionOutput { + Declare(DeclareTransactionOutput) = 0, + Deploy(DeployTransactionOutput) = 1, + DeployAccount(DeployAccountTransactionOutput) = 2, + Invoke(InvokeTransactionOutput) = 3, + L1Handler(L1HandlerTransactionOutput) = 4, + } pub struct TransactionSignature(pub Vec); pub struct TransactionVersion(pub StarkFelt); pub struct Version{ @@ -906,6 +878,50 @@ impl StorageSerde for BigUint { } } +impl StorageSerde for NoValue { + fn serialize_into(&self, _res: &mut impl std::io::Write) -> Result<(), StorageSerdeError> { + Ok(()) + } + + fn deserialize_from(_bytes: &mut impl std::io::Read) -> Option { + Some(Self) + } +} + +//////////////////////////////////////////////////////////////////////// +// Custom serialization for storage reduction. +//////////////////////////////////////////////////////////////////////// +// TODO(dvir): remove this when BlockNumber will be u32. +impl StorageSerde for BlockNumber { + fn serialize_into(&self, res: &mut impl std::io::Write) -> Result<(), StorageSerdeError> { + u32::try_from(self.0).expect("BlockNumber should fit into 32 bits.").serialize_into(res) + } + + fn deserialize_from(bytes: &mut impl std::io::Read) -> Option { + Some(BlockNumber(u32::deserialize_from(bytes)? as u64)) + } +} + +// This serialization write the offset as 3 bytes, which means that the maximum offset is ~16 +// million (1<<24 bytes). +impl StorageSerde for TransactionOffsetInBlock { + fn serialize_into( + &self, + res: &mut impl std::io::Write, + ) -> Result<(), crate::db::serialization::StorageSerdeError> { + let bytes = &self.0.to_be_bytes(); + res.write_all(&bytes[5..])?; + Ok(()) + } + + fn deserialize_from(bytes: &mut impl std::io::Read) -> Option { + let mut arr = [0u8; 8]; + bytes.read_exact(&mut arr[5..]).ok()?; + let index = usize::from_be_bytes(arr); + Some(Self(index)) + } +} + //////////////////////////////////////////////////////////////////////// // Custom serialization with compression. //////////////////////////////////////////////////////////////////////// @@ -1157,4 +1173,46 @@ auto_storage_serde_conditionally_compressed! { pub entry_point_selector: EntryPointSelector, pub calldata: Calldata, } + + pub struct DeclareTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } + + pub struct DeployAccountTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub contract_address: ContractAddress, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } + + pub struct DeployTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub contract_address: ContractAddress, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } + + pub struct InvokeTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } + + pub struct L1HandlerTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } } diff --git a/crates/papyrus_storage/src/serialization/serializers_test.rs b/crates/papyrus_storage/src/serialization/serializers_test.rs index 33e089783bd..39ff93de2ee 100644 --- a/crates/papyrus_storage/src/serialization/serializers_test.rs +++ b/crates/papyrus_storage/src/serialization/serializers_test.rs @@ -2,12 +2,13 @@ use std::fmt::Debug; use cairo_lang_casm::hints::CoreHintBase; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; +use papyrus_test_utils::{get_rng, read_json_file, GetTestInstance}; use pretty_assertions::assert_eq; use starknet_api::block::BlockNumber; use starknet_api::core::ContractAddress; use starknet_api::hash::StarkHash; use starknet_api::state::StorageKey; -use test_utils::{get_rng, read_json_file, GetTestInstance}; +use starknet_api::transaction::TransactionOffsetInBlock; use crate::db::serialization::StorageSerde; @@ -54,6 +55,28 @@ create_storage_serde_test!(StarkHash); create_storage_serde_test!(StorageKey); create_storage_serde_test!(u8); create_storage_serde_test!(usize); +create_storage_serde_test!(BlockNumber); +create_storage_serde_test!(TransactionOffsetInBlock); + +#[test] +fn transaction_offset_in_block_serialization_order() { + let offset_1 = TransactionOffsetInBlock(1); + let offset_256 = TransactionOffsetInBlock(256); + let mut serialized_1 = Vec::new(); + offset_1.serialize_into(&mut serialized_1).unwrap(); + let mut serialized_256 = Vec::new(); + offset_256.serialize_into(&mut serialized_256).unwrap(); + assert!(serialized_256 > serialized_1); +} + +#[test] +fn transaction_offset_in_block_serialization_max_value() { + let item = TransactionOffsetInBlock((1 << 24) - 1); + let mut buf = Vec::new(); + item.serialize_into(&mut buf).unwrap(); + let res = TransactionOffsetInBlock::deserialize_from(&mut buf.as_slice()).unwrap(); + assert_eq!(res, item); +} #[test] fn block_number_endianness() { diff --git a/crates/papyrus_storage/src/state/state_test.rs b/crates/papyrus_storage/src/state/state_test.rs index 778522c3d39..e85275af82e 100644 --- a/crates/papyrus_storage/src/state/state_test.rs +++ b/crates/papyrus_storage/src/state/state_test.rs @@ -1,6 +1,7 @@ use assert_matches::assert_matches; use cairo_lang_starknet_classes::casm_contract_class::CasmContractClass; use indexmap::{indexmap, IndexMap}; +use papyrus_test_utils::get_test_state_diff; use pretty_assertions::assert_eq; use starknet_api::block::BlockNumber; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; @@ -8,7 +9,6 @@ use starknet_api::deprecated_contract_class::ContractClass as DeprecatedContract use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::state::{ContractClass, StateNumber, StorageKey, ThinStateDiff}; use starknet_api::{patricia_key, stark_felt}; -use test_utils::get_test_state_diff; use crate::class::{ClassStorageReader, ClassStorageWriter}; use crate::compiled_class::{CasmStorageReader, CasmStorageWriter}; diff --git a/crates/papyrus_storage/src/test_instances.rs b/crates/papyrus_storage/src/test_instances.rs index c5fabd30e36..e2427f9d8ab 100644 --- a/crates/papyrus_storage/src/test_instances.rs +++ b/crates/papyrus_storage/src/test_instances.rs @@ -1,6 +1,6 @@ +use papyrus_test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use starknet_api::block::{BlockHash, BlockNumber, BlockTimestamp, GasPricePerToken}; use starknet_api::core::{ - ContractAddress, EventCommitment, GlobalRoot, ReceiptCommitment, @@ -11,29 +11,17 @@ use starknet_api::core::{ use starknet_api::data_availability::L1DataAvailabilityMode; use starknet_api::transaction::{ EventIndexInTransactionOutput, - ExecutionResources, - Fee, - MessageToL1, - TransactionExecutionStatus, + TransactionHash, TransactionOffsetInBlock, }; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; -use crate::body::events::{ - ThinDeclareTransactionOutput, - ThinDeployAccountTransactionOutput, - ThinDeployTransactionOutput, - ThinInvokeTransactionOutput, - ThinL1HandlerTransactionOutput, - ThinTransactionOutput, -}; use crate::body::TransactionIndex; use crate::compression_utils::IsCompressed; use crate::header::StorageBlockHeader; use crate::mmap_file::LocationInFile; use crate::state::data::IndexedDeprecatedContractClass; use crate::version::Version; -use crate::{EventIndex, MarkerKind, OffsetKind}; +use crate::{EventIndex, MarkerKind, OffsetKind, TransactionMetadata}; auto_impl_get_test_instance! { pub struct StorageBlockHeader { @@ -79,49 +67,10 @@ auto_impl_get_test_instance! { Casm = 2, DeprecatedContractClass = 3, } - pub struct ThinDeclareTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinDeployTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub contract_address: ContractAddress, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinDeployAccountTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub contract_address: ContractAddress, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinInvokeTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub struct ThinL1HandlerTransactionOutput { - pub actual_fee: Fee, - pub messages_sent: Vec, - pub events_contract_addresses: Vec, - pub execution_status: TransactionExecutionStatus, - pub execution_resources: ExecutionResources, - } - pub enum ThinTransactionOutput { - Declare(ThinDeclareTransactionOutput) = 0, - Deploy(ThinDeployTransactionOutput) = 1, - DeployAccount(ThinDeployAccountTransactionOutput) = 2, - Invoke(ThinInvokeTransactionOutput) = 3, - L1Handler(ThinL1HandlerTransactionOutput) = 4, + pub struct TransactionMetadata{ + pub tx_hash: TransactionHash, + pub tx_location: LocationInFile, + pub tx_output_location: LocationInFile, } struct TransactionIndex(pub BlockNumber, pub TransactionOffsetInBlock); pub struct Version{ diff --git a/crates/papyrus_storage/src/utils_test.rs b/crates/papyrus_storage/src/utils_test.rs index dbd48491569..ba2fbb903f9 100644 --- a/crates/papyrus_storage/src/utils_test.rs +++ b/crates/papyrus_storage/src/utils_test.rs @@ -3,13 +3,13 @@ use std::fs; use indexmap::indexmap; use metrics_exporter_prometheus::PrometheusBuilder; +use papyrus_test_utils::prometheus_is_contained; use pretty_assertions::assert_eq; use prometheus_parse::Value::{Counter, Gauge}; use starknet_api::block::BlockNumber; use starknet_api::core::{ClassHash, CompiledClassHash}; use starknet_api::hash::{StarkFelt, StarkHash}; use starknet_api::state::{ContractClass, ThinStateDiff}; -use test_utils::prometheus_is_contained; use super::update_storage_metrics; use crate::class::ClassStorageWriter; diff --git a/crates/papyrus_sync/Cargo.toml b/crates/papyrus_sync/Cargo.toml index 739e7545314..4d46a111942 100644 --- a/crates/papyrus_sync/Cargo.toml +++ b/crates/papyrus_sync/Cargo.toml @@ -26,7 +26,7 @@ papyrus_config = { path = "../papyrus_config", version = "0.4.0-dev.2" } papyrus_proc_macros = { path = "../papyrus_proc_macros", version = "0.4.0-dev.2" } reqwest = { workspace = true, features = ["json", "blocking"] } serde = { workspace = true, features = ["derive"] } -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } starknet_client = { path = "../starknet_client" } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } @@ -40,6 +40,6 @@ mockall.workspace = true papyrus_storage = { path = "../papyrus_storage", features = ["testing"] } pretty_assertions.workspace = true starknet_client = { path = "../starknet_client", features = ["testing"] } -starknet_api = { workspace = true, features = ["testing"] } -test_utils = { path = "../test_utils" } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } +papyrus_test_utils = { path = "../papyrus_test_utils" } tokio-stream.workspace = true diff --git a/crates/papyrus_sync/src/sync_test.rs b/crates/papyrus_sync/src/sync_test.rs index 25b78664bef..9d7565252eb 100644 --- a/crates/papyrus_sync/src/sync_test.rs +++ b/crates/papyrus_sync/src/sync_test.rs @@ -10,6 +10,7 @@ use papyrus_storage::base_layer::BaseLayerStorageReader; use papyrus_storage::header::HeaderStorageWriter; use papyrus_storage::test_utils::get_test_storage; use papyrus_storage::{StorageReader, StorageWriter}; +use papyrus_test_utils::{get_rng, GetTestInstance}; use pretty_assertions::assert_eq; use starknet_api::block::{BlockHash, BlockHeader, BlockNumber}; use starknet_api::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; @@ -26,7 +27,6 @@ use starknet_client::reader::objects::pending_data::{ use starknet_client::reader::objects::state::StateDiff as ClientStateDiff; use starknet_client::reader::objects::transaction::Transaction as ClientTransaction; use starknet_client::reader::{DeclaredClassHashEntry, PendingData}; -use test_utils::{get_rng, GetTestInstance}; use tokio::sync::RwLock; use crate::sources::base_layer::MockBaseLayerSourceTrait; diff --git a/crates/test_utils/Cargo.toml b/crates/papyrus_test_utils/Cargo.toml similarity index 88% rename from crates/test_utils/Cargo.toml rename to crates/papyrus_test_utils/Cargo.toml index 51fdc8fbd5f..9f540ba14cc 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/papyrus_test_utils/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "test_utils" +name = "papyrus_test_utils" version.workspace = true edition.workspace = true repository.workspace = true @@ -20,7 +20,7 @@ rand_chacha.workspace = true reqwest = { workspace = true, features = ["json"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"]} -starknet_api = { workspace = true, features = ["testing"] } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } tracing = { workspace = true, features = ["log"] } [dev-dependencies] diff --git a/crates/test_utils/src/lib.rs b/crates/papyrus_test_utils/src/lib.rs similarity index 95% rename from crates/test_utils/src/lib.rs rename to crates/papyrus_test_utils/src/lib.rs index d93a554f6c6..6527e81bcf2 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/papyrus_test_utils/src/lib.rs @@ -491,6 +491,13 @@ auto_impl_get_test_instance! { V2(DeclareTransactionV2) = 2, V3(DeclareTransactionV3) = 3, } + pub struct DeclareTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } pub struct DeclareTransactionV0V1 { pub max_fee: Fee, pub signature: TransactionSignature, @@ -523,6 +530,14 @@ auto_impl_get_test_instance! { V1(DeployAccountTransactionV1) = 0, V3(DeployAccountTransactionV3) = 1, } + pub struct DeployAccountTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub contract_address: ContractAddress, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } pub struct DeployAccountTransactionV1 { pub max_fee: Fee, pub signature: TransactionSignature, @@ -549,6 +564,14 @@ auto_impl_get_test_instance! { pub contract_address_salt: ContractAddressSalt, pub constructor_calldata: Calldata, } + pub struct DeployTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub contract_address: ContractAddress, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } pub struct DeprecatedEntryPoint { pub selector: EntryPointSelector, pub offset: EntryPointOffset, @@ -609,6 +632,13 @@ auto_impl_get_test_instance! { V1(InvokeTransactionV1) = 1, V3(InvokeTransactionV3) = 2, } + pub struct InvokeTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } pub struct InvokeTransactionV0 { pub max_fee: Fee, pub signature: TransactionSignature, @@ -646,6 +676,13 @@ auto_impl_get_test_instance! { pub entry_point_selector: EntryPointSelector, pub calldata: Calldata, } + pub struct L1HandlerTransactionOutput { + pub actual_fee: Fee, + pub messages_sent: Vec, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + } pub struct L1ToL2Payload(pub Vec); pub struct L2ToL1Payload(pub Vec); pub struct MessageToL1 { @@ -730,6 +767,13 @@ auto_impl_get_test_instance! { } pub struct TransactionHash(pub StarkHash); pub struct TransactionOffsetInBlock(pub usize); + pub enum TransactionOutput { + Declare(DeclareTransactionOutput) = 0, + Deploy(DeployTransactionOutput) = 1, + DeployAccount(DeployAccountTransactionOutput) = 2, + Invoke(InvokeTransactionOutput) = 3, + L1Handler(L1HandlerTransactionOutput) = 4, + } pub struct TransactionSignature(pub Vec); pub struct TransactionVersion(pub StarkFelt); pub struct TypedParameter { diff --git a/crates/test_utils/src/precision_test.rs b/crates/papyrus_test_utils/src/precision_test.rs similarity index 100% rename from crates/test_utils/src/precision_test.rs rename to crates/papyrus_test_utils/src/precision_test.rs diff --git a/crates/sequencing/papyrus_block_builder/Cargo.toml b/crates/sequencing/papyrus_block_builder/Cargo.toml new file mode 100644 index 00000000000..f1b002ab656 --- /dev/null +++ b/crates/sequencing/papyrus_block_builder/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "papyrus_block_builder" +version.workspace = true +edition.workspace = true +repository.workspace = true +license-file.workspace = true +description = "A block-builder for Starknet blocks" + +[dependencies] +papyrus_storage = { path = "../../papyrus_storage", version = "0.4.0-dev.2", features = ["testing"] } +starknet_api = { version = "0.12.0-dev.1" } +thiserror.workspace = true +tracing.workspace = true + +[dev-dependencies] +pretty_assertions.workspace = true +papyrus_test_utils = { path = "../../papyrus_test_utils" } diff --git a/crates/sequencing/papyrus_block_builder/src/lib.rs b/crates/sequencing/papyrus_block_builder/src/lib.rs new file mode 100644 index 00000000000..fc2d5bd7069 --- /dev/null +++ b/crates/sequencing/papyrus_block_builder/src/lib.rs @@ -0,0 +1,67 @@ +//! This crate contains a mock block-builder that echoes [`Starknet`] blocks. +//! +//! +//! [`Starknet`]: https://starknet.io/ + +use std::sync::mpsc::{self, Receiver}; + +use papyrus_storage::body::BodyStorageReader; +use papyrus_storage::StorageReader; +use starknet_api::block::BlockNumber; +use starknet_api::transaction::Transaction; +use tracing::instrument; + +#[cfg(test)] +mod test; + +/// A block builder. +struct BlockBuilder { + // A storage reader to read blocks from. Will be replaced with mempool. + #[allow(unused)] + storage_reader: StorageReader, +} + +pub trait BlockBuilderTrait { + fn build(&self, block_number: BlockNumber) -> BlockBuilderResult>; +} + +type BlockBuilderResult = Result; + +#[derive(thiserror::Error, Debug)] +pub enum BlockBuilderError { + #[error("Could not find a block with block number {}.", block_number)] + BlockNotFound { block_number: BlockNumber }, +} + +impl BlockBuilder { + /// Create a new block builder. + #[allow(unused)] + pub fn new(storage_reader: StorageReader) -> Self { + Self { storage_reader } + } +} + +impl BlockBuilderTrait for BlockBuilder { + #[instrument(skip(self), level = "debug")] + fn build(&self, block_number: BlockNumber) -> BlockBuilderResult> { + let (sender, receiver) = mpsc::channel(); + + // TODO: spawn a task to send the transactions and return the receiver immediately. + let block = self + .storage_reader + .begin_ro_txn() + .unwrap() + .get_block_transactions(block_number) + .unwrap(); + + match block { + Some(block) => { + for txn in block { + sender.send(txn).unwrap(); + } + Ok(receiver) + } + None => Err(BlockBuilderError::BlockNotFound { block_number }), + } + } +} diff --git a/crates/sequencing/papyrus_block_builder/src/test.rs b/crates/sequencing/papyrus_block_builder/src/test.rs new file mode 100644 index 00000000000..b4319778e46 --- /dev/null +++ b/crates/sequencing/papyrus_block_builder/src/test.rs @@ -0,0 +1,32 @@ +use papyrus_storage::body::BodyStorageWriter; +use papyrus_storage::test_utils::get_test_storage_by_scope; +use papyrus_storage::StorageScope; +use papyrus_test_utils::get_test_block; +use pretty_assertions::assert_eq; +use starknet_api::block::BlockNumber; + +use crate::{BlockBuilder, BlockBuilderTrait}; + +#[test] +fn block_proposer() { + let storage_scope = StorageScope::FullArchive; + let ((storage_reader, mut storage_writer), _temp_dir) = + get_test_storage_by_scope(storage_scope); + let block_body = get_test_block(2, Some(1), None, None).body; + storage_writer + .begin_rw_txn() + .unwrap() + .append_body(BlockNumber(0), block_body.clone()) + .unwrap() + .commit() + .unwrap(); + + let proposer = BlockBuilder::new(storage_reader); + let block_number = BlockNumber(0); + let proposal_receiver = proposer.build(block_number).unwrap(); + let proposal = proposal_receiver.iter().collect::>(); + assert_eq!(proposal, block_body.transactions.as_slice()); +} + +// TODO: add test for non-existing block. +// TODO: add test for sending info and dropping the sender before receiving. diff --git a/crates/sequencing/papyrus_consensus/Cargo.toml b/crates/sequencing/papyrus_consensus/Cargo.toml new file mode 100644 index 00000000000..8300609aa62 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "papyrus_consensus" +version.workspace = true +edition.workspace = true +repository.workspace = true +license-file.workspace = true +description = "Reach consensus for Starknet" + +[dependencies] +async-trait.workspace = true +futures.workspace = true +starknet_api = { version = "0.12.0-dev.1" } +tokio = { workspace = true, features = ["full"] } + +[dev-dependencies] +mockall.workspace = true diff --git a/crates/sequencing/papyrus_consensus/src/lib.rs b/crates/sequencing/papyrus_consensus/src/lib.rs new file mode 100644 index 00000000000..5ae64c35c9c --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/lib.rs @@ -0,0 +1,7 @@ +// TODO(matan): Remove dead code allowance at the end of milestone 1. +#[allow(dead_code)] +pub mod single_height_consensus; +#[cfg(test)] +pub mod test_utils; +#[allow(dead_code)] +pub mod types; diff --git a/crates/sequencing/papyrus_consensus/src/single_height_consensus.rs b/crates/sequencing/papyrus_consensus/src/single_height_consensus.rs new file mode 100644 index 00000000000..ac9d061bc51 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/single_height_consensus.rs @@ -0,0 +1,88 @@ +#[cfg(test)] +#[path = "single_height_consensus_test.rs"] +mod single_height_consensus_test; + +use std::sync::Arc; + +use futures::channel::{mpsc, oneshot}; +use futures::{SinkExt, StreamExt}; +use starknet_api::block::BlockNumber; + +use crate::types::{ + ConsensusBlock, + ConsensusContext, + NodeId, + PeeringConsensusMessage, + ProposalInit, +}; + +pub(crate) struct SingleHeightConsensus +where + BlockT: ConsensusBlock, +{ + height: BlockNumber, + context: Arc>, + validators: Vec, + id: NodeId, + to_peering_sender: mpsc::Sender>, + from_peering_receiver: mpsc::Receiver>, +} + +impl SingleHeightConsensus +where + BlockT: ConsensusBlock, +{ + pub(crate) async fn new( + height: BlockNumber, + context: Arc>, + id: NodeId, + to_peering_sender: mpsc::Sender>, + from_peering_receiver: mpsc::Receiver>, + ) -> Self { + let validators = context.validators(height).await; + Self { height, context, validators, id, to_peering_sender, from_peering_receiver } + } + + pub(crate) async fn run(mut self) -> BlockT { + // TODO(matan): In the future this logic will be encapsulated in the state machine, and SHC + // will await a signal from SHC to propose. + let proposer_id = self.context.proposer(&self.validators, self.height); + if proposer_id == self.id { self.propose().await } else { self.validate(proposer_id).await } + } + + async fn propose(&mut self) -> BlockT { + let (content_receiver, block_receiver) = self.context.build_proposal(self.height).await; + let (fin_sender, fin_receiver) = oneshot::channel(); + let init = ProposalInit { height: self.height, proposer: self.id }; + self.to_peering_sender + .send(PeeringConsensusMessage::Proposal((init, content_receiver, fin_receiver))) + .await + .expect("failed to send proposal to peering"); + let block = block_receiver.await.expect("failed to build block"); + // TODO(matan): Switch this to the Proposal signature. + fin_sender.send(block.id()).expect("failed to send block hash"); + block + } + + async fn validate(&mut self, proposer_id: NodeId) -> BlockT { + let msg = self + .from_peering_receiver + .next() + .await + .expect("Peering component disconnected from SingleHeightConsensus"); + + let (init, content_receiver, fin_receiver) = match msg { + PeeringConsensusMessage::Proposal((init, content_receiver, block_hash_receiver)) => { + (init, content_receiver, block_hash_receiver) + } + }; + assert_eq!(init.height, self.height); + assert_eq!(init.proposer, proposer_id); + let block_receiver = self.context.validate_proposal(self.height, content_receiver).await; + let block = block_receiver.await.expect("failed to build block"); + let fin = fin_receiver.await.expect("failed to receive block hash"); + // TODO(matan): Switch to signature validation. + assert_eq!(block.id(), fin); + block + } +} diff --git a/crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs b/crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs new file mode 100644 index 00000000000..a35b7ad7f78 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs @@ -0,0 +1,126 @@ +use std::sync::Arc; + +use futures::channel::{mpsc, oneshot}; +use futures::{SinkExt, StreamExt}; +use starknet_api::block::BlockNumber; +use tokio; + +use super::SingleHeightConsensus; +use crate::test_utils::{MockTestContext, TestBlock}; +use crate::types::{ConsensusBlock, NodeId, PeeringConsensusMessage, ProposalInit}; + +type Shc = SingleHeightConsensus; +type ProposalChunk = ::ProposalChunk; +type PeeringMessage = PeeringConsensusMessage; + +struct TestSetup { + pub context: MockTestContext, + pub shc_to_peering_sender: mpsc::Sender>, + pub shc_to_peering_receiver: mpsc::Receiver>, + pub peering_to_shc_sender: mpsc::Sender>, + pub peering_to_shc_receiver: mpsc::Receiver>, +} + +impl TestSetup { + fn new() -> TestSetup { + let (shc_to_peering_sender, shc_to_peering_receiver) = mpsc::channel(1); + let (peering_to_shc_sender, peering_to_shc_receiver) = mpsc::channel(1); + let context = MockTestContext::new(); + TestSetup { + context, + shc_to_peering_sender, + shc_to_peering_receiver, + peering_to_shc_sender, + peering_to_shc_receiver, + } + } + + // This should be called after all of the mock's expectations have been set. This is because SHC + // holds `Arc`. Setting mock expectations, though, require exclusive access + // (`&mut self`). + async fn new_shc( + self, + height: BlockNumber, + id: NodeId, + ) -> ( + Shc, + mpsc::Receiver>, + mpsc::Sender>, + ) { + let shc = Shc::new( + height, + Arc::new(self.context), + id, + self.shc_to_peering_sender, + self.peering_to_shc_receiver, + ) + .await; + (shc, self.shc_to_peering_receiver, self.peering_to_shc_sender) + } +} + +#[tokio::test] +async fn propose() { + let mut test_fields = TestSetup::new(); + let node_id: NodeId = 1; + let block = TestBlock { content: vec![1, 2, 3], id: 1 }; + // Set expectations for how the test should run: + test_fields.context.expect_validators().returning(move |_| vec![node_id, 2, 3, 4]); + test_fields.context.expect_proposer().returning(move |_, _| node_id); + let block_clone = block.clone(); + test_fields.context.expect_build_proposal().returning(move |_| { + // SHC doesn't actually handle the content, so ignore for unit tests. + let (_, content_receiver) = mpsc::channel(1); + let (block_sender, block_receiver) = oneshot::channel(); + block_sender.send(block_clone.clone()).unwrap(); + (content_receiver, block_receiver) + }); + + // Creation calls to `context.validators`. + let (shc, mut shc_to_peering_receiver, _) = test_fields.new_shc(BlockNumber(0), node_id).await; + + // This calls to `context.proposer` and `context.build_proposal`. + assert_eq!(shc.run().await, block); + + // Check what was sent to peering. We don't check the content stream as that is filled by + // ConsensusContext, not SHC. + let PeeringMessage::Proposal((init, _, block_id_receiver)) = + shc_to_peering_receiver.next().await.unwrap(); + assert_eq!(init, ProposalInit { height: BlockNumber(0), proposer: node_id }); + assert_eq!(block_id_receiver.await.unwrap(), block.id()); +} + +#[tokio::test] +async fn validate() { + let mut test_fields = TestSetup::new(); + let node_id: NodeId = 1; + let proposer: NodeId = 2; + let block = TestBlock { content: vec![1, 2, 3], id: 1 }; + // Set expectations for how the test should run: + test_fields.context.expect_validators().returning(move |_| vec![node_id, proposer, 3, 4]); + test_fields.context.expect_proposer().returning(move |_, _| proposer); + let block_clone = block.clone(); + test_fields.context.expect_validate_proposal().returning(move |_, _| { + let (block_sender, block_receiver) = oneshot::channel(); + block_sender.send(block_clone.clone()).unwrap(); + block_receiver + }); + + // Creation calls to `context.validators`. + let (shc, _, mut peering_to_shc_sender) = test_fields.new_shc(BlockNumber(0), node_id).await; + + // Send the proposal from the peer. + let (fin_sender, fin_receiver) = oneshot::channel(); + peering_to_shc_sender + .send(PeeringMessage::Proposal(( + ProposalInit { height: BlockNumber(0), proposer }, + mpsc::channel(1).1, // content - ignored by SHC. + fin_receiver, + ))) + .await + .unwrap(); + fin_sender.send(block.id()).unwrap(); + + // This calls to `context.proposer` and `context.build_proposal`. + assert_eq!(shc.run().await, block); +} diff --git a/crates/sequencing/papyrus_consensus/src/test_utils.rs b/crates/sequencing/papyrus_consensus/src/test_utils.rs new file mode 100644 index 00000000000..4b47b6b86f9 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/test_utils.rs @@ -0,0 +1,49 @@ +use async_trait::async_trait; +use futures::channel::{mpsc, oneshot}; +use mockall::mock; +use starknet_api::block::{BlockHash, BlockNumber}; +use starknet_api::hash::StarkFelt; + +use crate::types::{ConsensusBlock, ConsensusContext, NodeId}; + +/// Define a consensus block which can be used to enable auto mocking Context. +#[derive(Debug, PartialEq, Clone)] +pub struct TestBlock { + pub content: Vec, + pub id: u32, +} + +impl ConsensusBlock for TestBlock { + type ProposalChunk = u32; + type ProposalIter = std::vec::IntoIter; + + fn id(&self) -> BlockHash { + BlockHash(StarkFelt::try_from(self.id as u128).unwrap()) + } + + fn proposal_iter(&self) -> Self::ProposalIter { + self.content.clone().into_iter() + } +} + +// TODO(matan): When QSelf is supported, switch to automocking `ConsensusContext`. +mock! { + pub TestContext {} + + #[async_trait] + impl ConsensusContext for TestContext { + type Block = TestBlock; + + async fn build_proposal(&self, height: BlockNumber) -> ( + mpsc::Receiver, + oneshot::Receiver + ); + async fn validate_proposal( + &self, + height: BlockNumber, + content: mpsc::Receiver + ) -> oneshot::Receiver; + async fn validators(&self, height: BlockNumber) -> Vec; + fn proposer(&self, validators: &Vec, height: BlockNumber) -> NodeId; + } +} diff --git a/crates/sequencing/papyrus_consensus/src/types.rs b/crates/sequencing/papyrus_consensus/src/types.rs new file mode 100644 index 00000000000..15e151fef73 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/types.rs @@ -0,0 +1,141 @@ +#[cfg(test)] +#[path = "types_test.rs"] +mod types_test; + +use async_trait::async_trait; +use futures::channel::{mpsc, oneshot}; +use starknet_api::block::{BlockHash, BlockNumber}; + +/// Used to identify the node by consensus. +/// 1. This ID is derived from the id registered with Starknet's L2 staking contract. +/// 2. We must be able to derive the public key associated with this ID for the sake of validating +/// signatures. +// TODO(matan): Determine the actual type of NodeId. +pub type NodeId = u64; + +/// Interface that any concrete block type must implement to be used by consensus. +/// +/// In principle Consensus does not care about the content of a block. In practice though it will +/// need to perform certain activities with blocks: +/// 1. All proposals for a given height are held by consensus for book keeping, with only the +/// decided block returned to ConsensusContext. +/// 2. Tendermint may require re-broadcasting an old proposal [Line 16 of Algorithm 1]( https://arxiv.org/pdf/1807.04938) +// This trait was designed with the following in mind: +// 1. It must allow `ConsensusContext` to be object safe. This precludes generics. +// 2. Starknet blocks are expected to be quite large, and we expect consensus to hold something akin +// to a reference with a small stack size and cheap shallow cloning. +pub trait ConsensusBlock: Send { + /// The chunks of content returned when iterating the proposal. + type ProposalChunk; + /// Iterator for accessing the proposal's content. + // An associated type is used instead of returning `impl Iterator` due to object safety. + type ProposalIter: Iterator; + + /// Identifies the block for the sake of Consensus voting. + // The proposal's round must not be included in the ID, as, beyond being a detail of + // consensus, Tendermint must be able to progress a value across multiple rounds of a given + // height. + // + // Including a proposal's height in ID is optional from the perspective of consensus. + // Since the proposal as well as votes sign not only on the block ID but also the height at + // which they vote, not including height poses no security risk. Including it has no impact on + // Tendermint. + fn id(&self) -> BlockHash; + + /// Returns an iterator for streaming out this block as a proposal to other nodes. + // Note on the ownership and lifetime model. This call is done by reference, yet the returned + // iterator is implicitly an owning iterator. + // 1. Why did we not want reference iteration? This would require a lifetime to be part of the + // type definition for `ProposalIter` and therefore `ConsensusBlock`. This results in a lot + // of lifetime pollution making it much harder to work with this type; attempted both options + // from here: + // https://stackoverflow.com/questions/33734640/how-do-i-specify-lifetime-parameters-in-an-associated-type + // 2. Why is owning iteration reasonable? The expected use case for this is to stream out the + // proposal to other nodes, which implies ownership of data, not just a reference for + // internal use. We also expect the actual object implementing this trait to be itself a + // reference to the underlying data, and so returning an "owning" iterator to be relatively + // cheap. + // TODO(matan): Consider changing ConsensusBlock to `IntoIterator + Clone` and removing + // `proposal_iter`. + fn proposal_iter(&self) -> Self::ProposalIter; +} + +/// Interface for consensus to call out to the node. +// Why `Send + Sync`? +// 1. We expect multiple components within consensus to concurrently access the context. +// 2. The other option is for each component to have its own copy (i.e. clone) of the context, but +// this is object unsafe (Clone requires Sized). +// 3. Given that we see the context as basically a connector to other components in the node, the +// limitation of Sync to keep functions `&self` shouldn't be a problem. +#[async_trait] +pub trait ConsensusContext: Send + Sync { + /// The [block](`ConsensusBlock`) type built by `ConsensusContext` from a proposal. + // We use an associated type since consensus is indifferent to the actual content of a proposal, + // but we cannot use generics due to object safety. + type Block: ConsensusBlock; + + // TODO(matan): The oneshot for receiving the build block could be generalized to just be some + // future which returns a block. + + /// This function is called by consensus to request a block from the node. It expects that this + /// call will return immediately and that consensus can then stream in the block's content in + /// parallel to the block being built. + /// + /// Params: + /// - `height`: The height of the block to be built. Specifically this indicates the initial + /// state of the block. + /// + /// Returns: + /// - A receiver for the stream of the block's content. + /// - A receiver for the fully built block once ConsensusContext has finished streaming out the + /// content and building it. If the block fails to be built, the Sender will be dropped by + /// ConsensusContext. + async fn build_proposal( + &self, + height: BlockNumber, + ) -> ( + mpsc::Receiver<::ProposalChunk>, + oneshot::Receiver, + ); + + /// This function is called by consensus to validate a block. It expects that this call will + /// return immediately and that consensus can then stream in the block's content in parallel to + /// consensus continuing to handle other tasks. + /// + /// Params: + /// - `height`: The height of the block to be built. Specifically this indicates the initial + /// state of the block. + /// - A receiver for the stream of the block's content. + /// + /// Returns: + /// - A receiver for the fully built block. If a valid block cannot be built the Sender will be + /// dropped by ConsensusContext. + async fn validate_proposal( + &self, + height: BlockNumber, + content: mpsc::Receiver<::ProposalChunk>, + ) -> oneshot::Receiver; + + /// Get the set of validators for a given height. These are the nodes that can propose and vote + /// on blocks. + // TODO(matan): We expect this to change in the future to BTreeMap. Why? + // 1. Map - The nodes will have associated information (e.g. voting weight). + // 2. BTreeMap - We want a stable ordering of the nodes for deterministic leader selection. + async fn validators(&self, height: BlockNumber) -> Vec; + + /// Calculates the ID of the Proposer based on the inputs. + fn proposer(&self, validators: &Vec, height: BlockNumber) -> NodeId; +} + +#[derive(PartialEq, Debug)] +pub(crate) struct ProposalInit { + pub height: BlockNumber, + pub proposer: NodeId, +} + +// This type encapsulate the messages that are sent between the SingleHeightConsensus and the +// Peering components. +pub(crate) enum PeeringConsensusMessage { + // TODO(matan): Switch the oneshot channel to be a Signature when we add. + Proposal((ProposalInit, mpsc::Receiver, oneshot::Receiver)), +} diff --git a/crates/sequencing/papyrus_consensus/src/types_test.rs b/crates/sequencing/papyrus_consensus/src/types_test.rs new file mode 100644 index 00000000000..4305c708174 --- /dev/null +++ b/crates/sequencing/papyrus_consensus/src/types_test.rs @@ -0,0 +1,10 @@ +use crate::types::ConsensusContext; + +// This should cause compilation to fail if `ConsensusContext` is not object safe. Note that +// `ConsensusBlock` need not be object safe for this to work. +#[test] +fn check_object_safety() { + fn _check_context() -> Box> { + todo!() + } +} diff --git a/crates/starknet-api/src/block_hash.rs b/crates/starknet-api/src/block_hash.rs deleted file mode 100644 index a044de81193..00000000000 --- a/crates/starknet-api/src/block_hash.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod event_hash; diff --git a/crates/starknet-api/src/block_hash/event_hash.rs b/crates/starknet-api/src/block_hash/event_hash.rs deleted file mode 100644 index 015bf626e41..00000000000 --- a/crates/starknet-api/src/block_hash/event_hash.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::core::EventCommitment; -use crate::crypto::HashChain; -use crate::hash::StarkFelt; -use crate::transaction::{Event, TransactionHash}; - -#[cfg(test)] -#[path = "event_hash_test.rs"] -mod event_hash_test; - -/// Poseidon( -/// from_address, transaction_hash, -/// num_keys, key0, key1, ..., -/// num_contents, content0, content1, ... -/// ). -pub fn calculate_event_hash(event: &Event, transaction_hash: &TransactionHash) -> EventCommitment { - let keys = &event.content.keys.iter().map(|k| k.0).collect::>(); - let data = &event.content.data.0; - EventCommitment( - HashChain::new() - .chain(event.from_address.0.key()) - .chain(&transaction_hash.0) - .chain_size_and_elements(keys) - .chain_size_and_elements(data) - .get_poseidon_hash(), - ) -} diff --git a/crates/starknet-api/src/block_hash/event_hash_test.rs b/crates/starknet-api/src/block_hash/event_hash_test.rs deleted file mode 100644 index 055ecfbffb0..00000000000 --- a/crates/starknet-api/src/block_hash/event_hash_test.rs +++ /dev/null @@ -1,24 +0,0 @@ -use super::calculate_event_hash; -use crate::core::{ContractAddress, EventCommitment, PatriciaKey}; -use crate::hash::{StarkFelt, StarkHash}; -use crate::transaction::{Event, EventContent, EventData, EventKey, TransactionHash}; -use crate::{contract_address, patricia_key, stark_felt}; - -#[test] -fn test_event_hash_regression() { - let event = Event { - from_address: contract_address!(10_u8), - content: EventContent { - keys: [2_u8, 3].iter().map(|key| EventKey(stark_felt!(*key))).collect(), - data: EventData([4_u8, 5, 6].into_iter().map(StarkFelt::from).collect()), - }, - }; - let tx_hash = TransactionHash(stark_felt!("0x1234")); - - let expected_hash = EventCommitment( - StarkFelt::try_from("0x367807f532742a4dcbe2d8a47b974b22dd7496faa75edc64a3a5fdb6709057") - .unwrap(), - ); - - assert_eq!(expected_hash, calculate_event_hash(&event, &tx_hash)); -} diff --git a/crates/starknet-api/src/crypto_test.rs b/crates/starknet-api/src/crypto_test.rs deleted file mode 100644 index 4567df6a39f..00000000000 --- a/crates/starknet-api/src/crypto_test.rs +++ /dev/null @@ -1,25 +0,0 @@ -// Unittest for verify_message_signature - -use crate::crypto::{verify_message_hash_signature, PublicKey, Signature}; -use crate::hash::{poseidon_hash_array, StarkFelt}; -use crate::stark_felt; - -#[test] -fn signature_verification() { - // The signed message of block 4256. - let message_hash = poseidon_hash_array(&[ - stark_felt!("0x7d5db04c5ca2aea828180dc441afb1580e3cee7547a3567ced3aa5bb8b273c0"), - stark_felt!("0x64689c12248e1110af4b3af0e2b43cd51ad13e8855f10e37669e2a4baf919c6"), - ]); - // The signature of the message. - let signature = Signature { - r: stark_felt!("0x1b382bbfd693011c9b7692bc932b23ed9c288deb27c8e75772e172abbe5950c"), - s: stark_felt!("0xbe4438085057e1a7c704a0da3b30f7b8340fe3d24c86772abfd24aa597e42"), - }; - // The public key of the sequencer. - let public_key = - PublicKey(stark_felt!("0x48253ff2c3bed7af18bde0b611b083b39445959102d4947c51c4db6aa4f4e58")); - - let result = verify_message_hash_signature(&message_hash.0, &signature, &public_key).unwrap(); - assert!(result); -} diff --git a/crates/starknet-api/src/hash.rs b/crates/starknet-api/src/hash.rs deleted file mode 100644 index cb970021fd7..00000000000 --- a/crates/starknet-api/src/hash.rs +++ /dev/null @@ -1,307 +0,0 @@ -#[cfg(test)] -#[path = "hash_test.rs"] -mod hash_test; - -use std::fmt::{Debug, Display}; -use std::io::Error; - -use serde::{Deserialize, Serialize}; -use starknet_crypto::FieldElement; -use starknet_types_core::felt::Felt; -use starknet_types_core::hash::{Pedersen, Poseidon, StarkHash as StarknetTypesStarkHash}; - -use crate::serde_utils::{bytes_from_hex_str, hex_str_from_bytes, BytesAsHex, PrefixedBytesAsHex}; -use crate::{impl_from_through_intermediate, StarknetApiError}; - -/// Genesis state hash. -pub const GENESIS_HASH: &str = "0x0"; - -// Felt encoding constants. -const CHOOSER_FULL: u8 = 15; -const CHOOSER_HALF: u8 = 14; - -/// An alias for [`StarkFelt`]. -/// The output of the [Pedersen hash](https://docs.starknet.io/documentation/architecture_and_concepts/Hashing/hash-functions/#pedersen_hash). -pub type StarkHash = StarkFelt; - -/// Computes Pedersen hash using STARK curve on two elements, as defined -/// in -pub fn pedersen_hash(felt0: &StarkFelt, felt1: &StarkFelt) -> StarkHash { - Pedersen::hash(&Felt::from(felt0), &Felt::from(felt1)).into() -} - -/// Computes Pedersen hash using STARK curve on an array of elements, as defined -/// in -pub fn pedersen_hash_array(felts: &[StarkFelt]) -> StarkHash { - let current_hash = felts.iter().fold(Felt::from(0_u8), |current_hash, stark_felt| { - Pedersen::hash(¤t_hash, &Felt::from(stark_felt)) - }); - let data_len = Felt::from(u128::try_from(felts.len()).expect("Got 2^128 felts or more.")); - Pedersen::hash(¤t_hash, &data_len).into() -} - -/// A Poseidon hash. -#[derive( - Copy, Clone, Eq, PartialEq, Default, Hash, Deserialize, Serialize, PartialOrd, Ord, Debug, -)] -pub struct PoseidonHash(pub StarkFelt); - -impl Display for PoseidonHash { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - std::fmt::Display::fmt(&self.0, f) - } -} - -/// Computes Poseidon hash. -pub fn poseidon_hash_array(stark_felts: &[StarkFelt]) -> PoseidonHash { - // TODO(yair): Avoid allocating the vector of Felts. - let as_felts = stark_felts.iter().map(Felt::from).collect::>(); - PoseidonHash(Poseidon::hash_array(as_felts.as_slice()).into()) -} - -// TODO: Move to a different crate. -/// The StarkNet [field element](https://docs.starknet.io/documentation/architecture_and_concepts/Hashing/hash-functions/#domain_and_range). -#[derive(Copy, Clone, Eq, PartialEq, Default, Hash, Deserialize, Serialize, PartialOrd, Ord)] -#[serde(try_from = "PrefixedBytesAsHex<32_usize>", into = "PrefixedBytesAsHex<32_usize>")] -pub struct StarkFelt([u8; 32]); - -impl StarkFelt { - /// Returns a new [`StarkFelt`]. - pub fn new(bytes: [u8; 32]) -> Result { - // msb nibble must be 0. This is not a tight bound. - if bytes[0] < 0x10 { - return Ok(Self(bytes)); - } - Err(StarknetApiError::OutOfRange { string: hex_str_from_bytes::<32, true>(bytes) }) - } - - /// Returns a new *unchecked* [`StarkFelt`] - /// - /// # Safety - /// - /// To avoid undefined behavior, refer to [`StarkFelt`] struct's docstring - /// for the required constraints on the `bytes` argument, or use [`StarkFelt::new`] instead of - /// this method. - /// - /// # Usage - /// - /// Most of the time you should use `new` instead, but it comes in handy for a few cases: - /// - creating instances of `StarkFelt` at compile time - /// - implementing `From for StarkFelt` for types that have a smaller binary representation - /// than `StarkFelt` - pub const fn new_unchecked(bytes: [u8; 32]) -> StarkFelt { - Self(bytes) - } - - /// [StarkFelt] constant that's equal to 0. - pub const ZERO: Self = { Self::from_u128(0_u128) }; - - /// [StarkFelt] constant that's equal to 1. - pub const ONE: Self = { Self::from_u128(1_u128) }; - - /// [StarkFelt] constant that's equal to 2. - pub const TWO: Self = { Self::from_u128(2_u128) }; - - /// [StarkFelt] constant that's equal to 3. - pub const THREE: Self = { Self::from_u128(3_u128) }; - - pub const fn from_u128(val: u128) -> Self { - let mut bytes = [0u8; 32]; - let val_bytes = val.to_be_bytes(); - let mut index = 16; - while index < 32 { - bytes[index] = val_bytes[index - 16]; - index += 1; - } - Self(bytes) - } - - /// Storage efficient serialization for field elements. - pub fn serialize(&self, res: &mut impl std::io::Write) -> Result<(), Error> { - // We use the fact that bytes[0] < 0x10 and encode the size of the felt in the 4 most - // significant bits of the serialization, which we call `chooser`. We assume that 128 bit - // felts are prevalent (because of how uint256 is encoded in felts). - - // The first i for which nibbles 2i+1, 2i+2 are nonzero. Note that the first nibble is - // always 0. - let mut first_index = 31; - for i in 0..32 { - let value = self.0[i]; - if value == 0 { - continue; - } else if value < 16 { - // Can encode the chooser and the value on a single byte. - first_index = i; - } else { - // The chooser is encoded with the first nibble of the value. - first_index = i - 1; - } - break; - } - let chooser = if first_index < 15 { - // For 34 up to 63 nibble felts: chooser == 15, serialize using 32 bytes. - first_index = 0; - CHOOSER_FULL - } else if first_index < 18 { - // For 28 up to 33 nibble felts: chooser == 14, serialize using 17 bytes. - first_index = 15; - CHOOSER_HALF - } else { - // For up to 27 nibble felts: serialize the lower 1 + (chooser * 2) nibbles of the felt - // using chooser + 1 bytes. - (31 - first_index) as u8 - }; - res.write_all(&[(chooser << 4) | self.0[first_index]])?; - res.write_all(&self.0[first_index + 1..])?; - Ok(()) - } - - /// Storage efficient deserialization for field elements. - pub fn deserialize(bytes: &mut impl std::io::Read) -> Option { - let mut res = [0u8; 32]; - - bytes.read_exact(&mut res[..1]).ok()?; - let first = res[0]; - let chooser: u8 = first >> 4; - let first = first & 0x0f; - - let first_index = if chooser == CHOOSER_FULL { - 0 - } else if chooser == CHOOSER_HALF { - 15 - } else { - (31 - chooser) as usize - }; - res[0] = 0; - res[first_index] = first; - bytes.read_exact(&mut res[first_index + 1..]).ok()?; - Some(Self(res)) - } - - pub fn bytes(&self) -> &[u8; 32] { - &self.0 - } - - fn str_format(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let s = format!("0x{}", hex::encode(self.0)); - f.debug_tuple("StarkFelt").field(&s).finish() - } -} - -impl TryFrom> for StarkFelt { - type Error = StarknetApiError; - fn try_from(val: PrefixedBytesAsHex<32_usize>) -> Result { - StarkFelt::new(val.0) - } -} - -impl TryFrom<&str> for StarkFelt { - type Error = StarknetApiError; - fn try_from(val: &str) -> Result { - let val = val.trim_start_matches("0x"); - let bytes = bytes_from_hex_str::<32, false>(val)?; - Self::new(bytes) - } -} - -impl From for StarkFelt { - fn from(val: u128) -> Self { - Self::from_u128(val) - } -} - -impl_from_through_intermediate!(u128, StarkFelt, u8, u16, u32, u64); - -impl From for StarkFelt { - fn from(felt: Felt) -> Self { - // Should not fail. - Self::new(felt.to_bytes_be()).expect("Convert Felt to StarkFelt.") - } -} - -impl From for StarkFelt { - fn from(val: usize) -> Self { - Felt::from(val).into() - } -} - -impl From<&StarkFelt> for Felt { - fn from(felt: &StarkFelt) -> Self { - Self::from_bytes_be(&felt.0) - } -} - -impl From for StarkFelt { - fn from(fe: FieldElement) -> Self { - // Should not fail. - Self::new(fe.to_bytes_be()).expect("Convert FieldElement to StarkFelt.") - } -} - -impl From for FieldElement { - fn from(felt: StarkFelt) -> Self { - // Should not fail. - Self::from_bytes_be(&felt.0).expect("Convert StarkFelf to FieldElement.") - } -} - -impl From for PrefixedBytesAsHex<32_usize> { - fn from(felt: StarkFelt) -> Self { - BytesAsHex(felt.0) - } -} - -// TODO(Arni, 25/6/2023): Remove impl TryFrom for usize. Leave only one conversion from -// StarkFelt to integer type. -impl TryFrom for usize { - type Error = StarknetApiError; - fn try_from(felt: StarkFelt) -> Result { - const COMPLIMENT_OF_USIZE: usize = - std::mem::size_of::() - std::mem::size_of::(); - - let (rest, usize_bytes) = felt.bytes().split_at(COMPLIMENT_OF_USIZE); - if rest != [0u8; COMPLIMENT_OF_USIZE] { - return Err(StarknetApiError::OutOfRange { string: felt.to_string() }); - } - - Ok(usize::from_be_bytes( - usize_bytes.try_into().expect("usize_bytes should be of size usize."), - )) - } -} - -// TODO(Arni, 1/1/2024): This is a Hack. Remove this and implement arethmetics for StarkFelt. -impl TryFrom for u64 { - type Error = StarknetApiError; - fn try_from(felt: StarkFelt) -> Result { - const COMPLIMENT_OF_U64: usize = 24; // 32 - 8 - let (rest, u64_bytes) = felt.bytes().split_at(COMPLIMENT_OF_U64); - if rest != [0u8; COMPLIMENT_OF_U64] { - return Err(StarknetApiError::OutOfRange { string: felt.to_string() }); - } - - let bytes: [u8; 8] = u64_bytes.try_into().unwrap(); - Ok(u64::from_be_bytes(bytes)) - } -} - -impl Debug for StarkFelt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.str_format(f) - } -} - -impl Display for StarkFelt { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "0x{}", hex::encode(self.0)) - } -} - -/// A utility macro to create a [`StarkFelt`] from a hex string representation. -#[cfg(any(feature = "testing", test))] -#[macro_export] -macro_rules! stark_felt { - ($s:expr) => { - StarkFelt::try_from($s).unwrap() - }; -} diff --git a/crates/starknet-api/src/hash_test.rs b/crates/starknet-api/src/hash_test.rs deleted file mode 100644 index f510aef3862..00000000000 --- a/crates/starknet-api/src/hash_test.rs +++ /dev/null @@ -1,93 +0,0 @@ -use assert_matches::assert_matches; - -use crate::hash::{pedersen_hash, pedersen_hash_array, StarkFelt}; -use crate::transaction::Fee; -use crate::{stark_felt, StarknetApiError}; - -#[test] -fn pedersen_hash_correctness() { - // Test vectors from https://github.com/starkware-libs/crypto-cpp/blob/master/src/starkware/crypto/pedersen_hash_test.cc - let a = stark_felt!("0x03d937c035c878245caf64531a5756109c53068da139362728feb561405371cb"); - let b = stark_felt!("0x0208a0a10250e382e1e4bbe2880906c2791bf6275695e02fbbc6aeff9cd8b31a"); - let expected = - stark_felt!("0x030e480bed5fe53fa909cc0f8c4d99b8f9f2c016be4c41e13a4848797979c662"); - assert_eq!(pedersen_hash(&a, &b), expected); -} - -#[test] -fn pedersen_hash_array_correctness() { - let a = stark_felt!("0xaa"); - let b = stark_felt!("0xbb"); - let c = stark_felt!("0xcc"); - let expected = pedersen_hash( - &pedersen_hash(&pedersen_hash(&pedersen_hash(&stark_felt!("0x0"), &a), &b), &c), - &stark_felt!("0x3"), - ); - assert_eq!(pedersen_hash_array(&[a, b, c]), expected); -} - -#[test] -fn hash_macro() { - assert_eq!( - stark_felt!("0x123"), - StarkFelt::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0x1, 0x23 - ]) - .unwrap() - ); -} - -#[test] -fn hash_json_serde() { - let hash = stark_felt!("0x123"); - assert_eq!(hash, serde_json::from_str(&serde_json::to_string(&hash).unwrap()).unwrap()); -} - -#[test] -fn hash_serde() { - fn enc_len(n_nibbles: usize) -> usize { - match n_nibbles { - 0..=27 => n_nibbles / 2 + 1, - 28..=33 => 17, - _ => 32, - } - } - - // 64 nibbles are invalid. - for n_nibbles in 0..64 { - let mut bytes = [0u8; 32]; - // Set all nibbles to 0xf. - for i in 0..n_nibbles { - bytes[31 - (i >> 1)] |= 15 << (4 * (i & 1)); - } - let h = StarkFelt::new(bytes).unwrap(); - let mut res = Vec::new(); - assert!(h.serialize(&mut res).is_ok()); - assert_eq!(res.len(), enc_len(n_nibbles)); - let mut reader = &res[..]; - let d = StarkFelt::deserialize(&mut reader).unwrap(); - assert_eq!(bytes, d.0); - } -} - -#[test] -fn fee_to_starkfelt() { - let fee = Fee(u128::MAX); - assert_eq!(format!("{}", StarkFelt::from(fee)), format!("{:#066x}", fee.0)); -} - -#[test] -fn felt_to_u64_and_back() { - // Positive flow. - let value = u64::MAX; - let felt: StarkFelt = value.into(); - let new_value: u64 = felt.try_into().unwrap(); - assert_eq!(value, new_value); - - // Negative flow. - let value: u128 = u128::from(u64::MAX) + 1; - let another_felt: StarkFelt = value.into(); - let err = u64::try_from(another_felt).unwrap_err(); - assert_matches!(err, StarknetApiError::OutOfRange { .. }); -} diff --git a/crates/starknet-api/Cargo.toml b/crates/starknet_api/Cargo.toml similarity index 90% rename from crates/starknet-api/Cargo.toml rename to crates/starknet_api/Cargo.toml index 7f6ca419854..685e6ca8911 100644 --- a/crates/starknet-api/Cargo.toml +++ b/crates/starknet_api/Cargo.toml @@ -4,12 +4,12 @@ version.workspace = true edition.workspace = true repository.workspace = true license-file.workspace = true -description = "Starknet Rust types related to computation and execution." [features] testing = [] [dependencies] +bitvec = "1.0.1" cairo-lang-starknet-classes = "2.6.0" derive_more = "0.99.17" hex = "0.4.3" @@ -19,6 +19,7 @@ once_cell = "1.17.1" primitive-types = { version = "0.12.1", features = ["serde"] } serde = { version = "1.0.130", features = ["derive", "rc"] } serde_json = "1.0.81" +sha3 = "0.10.8" starknet-crypto = "0.5.1" starknet-types-core = { version = "0.0.11", features = ["hash"] } strum = "0.24.1" diff --git a/crates/starknet-api/scripts/rust_fmt.sh b/crates/starknet_api/scripts/rust_fmt.sh similarity index 100% rename from crates/starknet-api/scripts/rust_fmt.sh rename to crates/starknet_api/scripts/rust_fmt.sh diff --git a/crates/starknet-api/src/block.rs b/crates/starknet_api/src/block.rs similarity index 86% rename from crates/starknet-api/src/block.rs rename to crates/starknet_api/src/block.rs index 0f2ff5f6654..489f869a66a 100644 --- a/crates/starknet-api/src/block.rs +++ b/crates/starknet_api/src/block.rs @@ -6,6 +6,7 @@ use std::fmt::Display; use derive_more::Display; use serde::{Deserialize, Serialize}; +use starknet_types_core::hash::{Poseidon, StarkHash as CoreStarkHash}; use crate::core::{ EventCommitment, @@ -16,15 +17,17 @@ use crate::core::{ StateDiffCommitment, TransactionCommitment, }; -use crate::crypto::{verify_message_hash_signature, CryptoError, Signature}; +use crate::crypto::utils::{verify_message_hash_signature, CryptoError, Signature}; use crate::data_availability::L1DataAvailabilityMode; -use crate::hash::{poseidon_hash_array, StarkHash}; +use crate::hash::StarkHash; use crate::serde_utils::{BytesAsHex, PrefixedBytesAsHex}; use crate::transaction::{Transaction, TransactionHash, TransactionOutput}; /// A block. #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct Block { + // TODO: Consider renaming to BlockWithCommitments, for the header use BlockHeaderWithoutHash + // instead of BlockHeader, and add BlockHeaderCommitments and BlockHash fields. pub header: BlockHeader, pub body: BlockBody, } @@ -80,6 +83,20 @@ pub struct BlockHeader { pub starknet_version: StarknetVersion, } +/// The header of a [Block](`crate::block::Block`) without hashing. +#[derive(Debug, Default, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] +pub struct BlockHeaderWithoutHash { + pub parent_hash: BlockHash, + pub block_number: BlockNumber, + pub l1_gas_price: GasPricePerToken, + pub l1_data_gas_price: GasPricePerToken, + pub state_root: GlobalRoot, + pub sequencer: SequencerContractAddress, + pub timestamp: BlockTimestamp, + pub l1_da_mode: L1DataAvailabilityMode, + pub starknet_version: StarknetVersion, +} + /// The [transactions](`crate::transaction::Transaction`) and their /// [outputs](`crate::transaction::TransactionOutput`) in a [block](`crate::block::Block`). #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -222,8 +239,8 @@ pub fn verify_block_signature( state_diff_commitment: &GlobalRoot, block_hash: &BlockHash, ) -> Result { - let message_hash = poseidon_hash_array(&[block_hash.0, state_diff_commitment.0]); - verify_message_hash_signature(&message_hash.0, &signature.0, &sequencer_pub_key.0).map_err( + let message_hash = Poseidon::hash_array(&[block_hash.0, state_diff_commitment.0]); + verify_message_hash_signature(&message_hash, &signature.0, &sequencer_pub_key.0).map_err( |err| BlockVerificationError::BlockSignatureVerificationFailed { block_hash: *block_hash, error: err, diff --git a/crates/starknet_api/src/block_hash.rs b/crates/starknet_api/src/block_hash.rs new file mode 100644 index 00000000000..6a476942061 --- /dev/null +++ b/crates/starknet_api/src/block_hash.rs @@ -0,0 +1,8 @@ +pub mod block_hash_calculator; +pub mod event_commitment; +pub mod receipt_commitment; +pub mod state_diff_hash; +pub mod transaction_commitment; + +#[cfg(test)] +pub mod test_utils; diff --git a/crates/starknet_api/src/block_hash/block_hash_calculator.rs b/crates/starknet_api/src/block_hash/block_hash_calculator.rs new file mode 100644 index 00000000000..2f5b268196e --- /dev/null +++ b/crates/starknet_api/src/block_hash/block_hash_calculator.rs @@ -0,0 +1,142 @@ +use once_cell::sync::Lazy; +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::Poseidon; + +use super::event_commitment::{calculate_events_commitment, EventLeafElement}; +use super::receipt_commitment::{calculate_receipt_commitment, ReceiptElement}; +use super::state_diff_hash::calculate_state_diff_hash; +use super::transaction_commitment::{calculate_transactions_commitment, TransactionLeafElement}; +use crate::block::{BlockHash, BlockHeaderWithoutHash}; +use crate::core::{EventCommitment, ReceiptCommitment, StateDiffCommitment, TransactionCommitment}; +use crate::crypto::utils::HashChain; +use crate::data_availability::L1DataAvailabilityMode; +use crate::state::ThinStateDiff; +use crate::transaction::{TransactionHash, TransactionOutputCommon, TransactionSignature}; +use crate::transaction_hash::ascii_as_felt; + +#[cfg(test)] +#[path = "block_hash_calculator_test.rs"] +mod block_hash_calculator_test; + +static STARKNET_BLOCK_HASH0: Lazy = Lazy::new(|| { + ascii_as_felt("STARKNET_BLOCK_HASH0").expect("ascii_as_felt failed for 'STARKNET_BLOCK_HASH0'") +}); + +pub struct TransactionHashingData { + pub transaction_signature: Option, + pub transaction_output: TransactionOutputCommon, + pub transaction_hash: TransactionHash, +} + +/// Commitments of a block. +pub struct BlockHeaderCommitments { + pub transactions_commitment: TransactionCommitment, + pub events_commitment: EventCommitment, + pub receipts_commitment: ReceiptCommitment, + pub state_diff_commitment: StateDiffCommitment, + pub concatenated_counts: Felt, +} + +/// Poseidon ( +/// “STARKNET_BLOCK_HASH0”, block_number, global_state_root, sequencer_address, +/// block_timestamp, concat_counts, state_diff_hash, transaction_commitment, +/// event_commitment, receipt_commitment, gas_price_wei, gas_price_fri, +/// data_gas_price_wei, data_gas_price_fri, starknet_version, 0, parent_block_hash +/// ). +pub fn calculate_block_hash( + header: BlockHeaderWithoutHash, + block_commitments: BlockHeaderCommitments, +) -> BlockHash { + BlockHash( + HashChain::new() + .chain(&STARKNET_BLOCK_HASH0) + .chain(&header.block_number.0.into()) + .chain(&header.state_root.0) + .chain(&header.sequencer.0) + .chain(&header.timestamp.0.into()) + .chain(&block_commitments.concatenated_counts) + .chain(&block_commitments.state_diff_commitment.0.0) + .chain(&block_commitments.transactions_commitment.0) + .chain(&block_commitments.events_commitment.0) + .chain(&block_commitments.receipts_commitment.0) + .chain(&header.l1_gas_price.price_in_wei.0.into()) + .chain(&header.l1_gas_price.price_in_fri.0.into()) + .chain(&header.l1_data_gas_price.price_in_wei.0.into()) + .chain(&header.l1_data_gas_price.price_in_fri.0.into()) + .chain(&ascii_as_felt(&header.starknet_version.0).expect("Expect ASCII version")) + .chain(&Felt::ZERO) + .chain(&header.parent_hash.0) + .get_poseidon_hash(), + ) +} + +/// Calculates the commitments of the transactions data for the block hash. +pub fn calculate_block_commitments( + transactions_data: &[TransactionHashingData], + state_diff: &ThinStateDiff, + l1_da_mode: L1DataAvailabilityMode, +) -> BlockHeaderCommitments { + let transaction_leaf_elements: Vec = + transactions_data.iter().map(TransactionLeafElement::from).collect(); + let transactions_commitment = + calculate_transactions_commitment::(&transaction_leaf_elements); + + let event_leaf_elements: Vec = transactions_data + .iter() + .flat_map(|transaction_data| { + transaction_data.transaction_output.events.iter().map(|event| EventLeafElement { + event: event.clone(), + transaction_hash: transaction_data.transaction_hash, + }) + }) + .collect(); + let events_commitment = calculate_events_commitment::(&event_leaf_elements); + + let receipt_elements: Vec = + transactions_data.iter().map(ReceiptElement::from).collect(); + let receipts_commitment = calculate_receipt_commitment::(&receipt_elements); + let state_diff_commitment = calculate_state_diff_hash(state_diff); + let concatenated_counts = concat_counts( + transactions_data.len(), + event_leaf_elements.len(), + state_diff.len(), + l1_da_mode, + ); + BlockHeaderCommitments { + transactions_commitment, + events_commitment, + receipts_commitment, + state_diff_commitment, + concatenated_counts, + } +} + +// A single felt: [ +// transaction_count (64 bits) | event_count (64 bits) | state_diff_length (64 bits) +// | L1 data availability mode: 0 for calldata, 1 for blob (1 bit) | 0 ... +// ]. +fn concat_counts( + transaction_count: usize, + event_count: usize, + state_diff_length: usize, + l1_data_availability_mode: L1DataAvailabilityMode, +) -> Felt { + let l1_data_availability_byte: u8 = match l1_data_availability_mode { + L1DataAvailabilityMode::Calldata => 0, + L1DataAvailabilityMode::Blob => 0b10000000, + }; + let concat_bytes = [ + to_64_bits(transaction_count).as_slice(), + to_64_bits(event_count).as_slice(), + to_64_bits(state_diff_length).as_slice(), + &[l1_data_availability_byte], + &[0_u8; 7], // zero padding + ] + .concat(); + Felt::from_bytes_be_slice(concat_bytes.as_slice()) +} + +fn to_64_bits(num: usize) -> [u8; 8] { + let sized_transaction_count: u64 = num.try_into().expect("Expect usize is at most 8 bytes"); + sized_transaction_count.to_be_bytes() +} diff --git a/crates/starknet_api/src/block_hash/block_hash_calculator_test.rs b/crates/starknet_api/src/block_hash/block_hash_calculator_test.rs new file mode 100644 index 00000000000..968e8343807 --- /dev/null +++ b/crates/starknet_api/src/block_hash/block_hash_calculator_test.rs @@ -0,0 +1,61 @@ +use starknet_types_core::felt::Felt; + +use super::concat_counts; +use crate::block::{ + BlockHash, + BlockHeaderWithoutHash, + BlockNumber, + BlockTimestamp, + GasPrice, + GasPricePerToken, + StarknetVersion, +}; +use crate::block_hash::block_hash_calculator::{ + calculate_block_commitments, + calculate_block_hash, + TransactionHashingData, +}; +use crate::block_hash::test_utils::{get_state_diff, get_transaction_output}; +use crate::core::{ContractAddress, GlobalRoot, PatriciaKey, SequencerContractAddress}; +use crate::data_availability::L1DataAvailabilityMode; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; +use crate::transaction::{TransactionHash, TransactionSignature}; + +#[test] +fn test_block_hash_regression() { + let block_header = BlockHeaderWithoutHash { + block_number: BlockNumber(1_u64), + state_root: GlobalRoot(Felt::from(2_u8)), + sequencer: SequencerContractAddress(ContractAddress(PatriciaKey::from(3_u8))), + timestamp: BlockTimestamp(4), + l1_da_mode: L1DataAvailabilityMode::Blob, + l1_gas_price: GasPricePerToken { price_in_fri: GasPrice(6), price_in_wei: GasPrice(7) }, + l1_data_gas_price: GasPricePerToken { + price_in_fri: GasPrice(10), + price_in_wei: GasPrice(9), + }, + starknet_version: StarknetVersion("10".to_owned()), + parent_hash: BlockHash(Felt::from(11_u8)), + }; + let transactions_data = vec![TransactionHashingData { + transaction_signature: Some(TransactionSignature(vec![Felt::TWO, Felt::THREE])), + transaction_output: get_transaction_output(), + transaction_hash: TransactionHash(Felt::ONE), + }]; + + let state_diff = get_state_diff(); + let block_commitments = + calculate_block_commitments(&transactions_data, &state_diff, block_header.l1_da_mode); + + let expected_hash = felt!("0x061e4998d51a248f1d0288d7e17f6287757b0e5e6c5e1e58ddf740616e312134"); + + assert_eq!(BlockHash(expected_hash), calculate_block_hash(block_header, block_commitments),); +} + +#[test] +fn concat_counts_test() { + let concated = concat_counts(4, 3, 2, L1DataAvailabilityMode::Blob); + let expected_felt = felt!("0x0000000000000004000000000000000300000000000000028000000000000000"); + assert_eq!(concated, expected_felt) +} diff --git a/crates/starknet_api/src/block_hash/event_commitment.rs b/crates/starknet_api/src/block_hash/event_commitment.rs new file mode 100644 index 00000000000..344dc2559c5 --- /dev/null +++ b/crates/starknet_api/src/block_hash/event_commitment.rs @@ -0,0 +1,42 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::StarkHash; + +use crate::core::EventCommitment; +use crate::crypto::patricia_hash::calculate_root; +use crate::crypto::utils::HashChain; +use crate::transaction::{Event, TransactionHash}; + +#[cfg(test)] +#[path = "event_commitment_test.rs"] +mod event_commitment_test; + +/// The elements used to calculate a leaf in the transactions Patricia tree. +#[derive(Clone)] +pub struct EventLeafElement { + pub(crate) event: Event, + pub(crate) transaction_hash: TransactionHash, +} + +/// Returns the root of a Patricia tree where each leaf is an event hash. +pub fn calculate_events_commitment( + event_leaf_elements: &[EventLeafElement], +) -> EventCommitment { + let event_leaves = event_leaf_elements.iter().map(calculate_event_hash).collect(); + EventCommitment(calculate_root::(event_leaves)) +} + +// Poseidon( +// from_address, transaction_hash, +// num_keys, key0, key1, ..., +// num_contents, content0, content1, ... +// ). +fn calculate_event_hash(event_leaf_element: &EventLeafElement) -> Felt { + let keys = &event_leaf_element.event.content.keys.iter().map(|k| k.0).collect::>(); + let data = &event_leaf_element.event.content.data.0; + HashChain::new() + .chain(event_leaf_element.event.from_address.0.key()) + .chain(&event_leaf_element.transaction_hash.0) + .chain_size_and_elements(keys) + .chain_size_and_elements(data) + .get_poseidon_hash() +} diff --git a/crates/starknet_api/src/block_hash/event_commitment_test.rs b/crates/starknet_api/src/block_hash/event_commitment_test.rs new file mode 100644 index 00000000000..777d2b7271c --- /dev/null +++ b/crates/starknet_api/src/block_hash/event_commitment_test.rs @@ -0,0 +1,45 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::Poseidon; + +use super::{calculate_event_hash, calculate_events_commitment, EventLeafElement}; +use crate::core::{ContractAddress, EventCommitment, PatriciaKey}; +use crate::hash::{FeltConverter, TryIntoFelt}; +use crate::transaction::{Event, EventContent, EventData, EventKey, TransactionHash}; +use crate::{contract_address, felt, patricia_key}; + +#[test] +fn test_events_commitment_regression() { + let event_leaf_elements = + [get_event_leaf_element(0), get_event_leaf_element(1), get_event_leaf_element(2)]; + + let expected_root = felt!("0x069bb140ddbbeb01d81c7201ecfb933031306e45dab9c77ff9f9ba3cd4c2b9c3"); + + assert_eq!( + EventCommitment(expected_root), + calculate_events_commitment::(&event_leaf_elements), + ); +} + +#[test] +fn test_event_hash_regression() { + let event_leaf_element = get_event_leaf_element(2); + + let expected_hash = felt!("0x367807f532742a4dcbe2d8a47b974b22dd7496faa75edc64a3a5fdb6709057"); + + assert_eq!(expected_hash, calculate_event_hash(&event_leaf_element)); +} + +fn get_event_leaf_element(seed: u8) -> EventLeafElement { + EventLeafElement { + event: Event { + from_address: contract_address!(format!("{:x}", seed + 8).as_str()), + content: EventContent { + keys: [seed, seed + 1].iter().map(|key| EventKey(Felt::from(*key))).collect(), + data: EventData( + [seed + 2, seed + 3, seed + 4].into_iter().map(Felt::from).collect(), + ), + }, + }, + transaction_hash: TransactionHash(felt!("0x1234")), + } +} diff --git a/crates/starknet_api/src/block_hash/receipt_commitment.rs b/crates/starknet_api/src/block_hash/receipt_commitment.rs new file mode 100644 index 00000000000..b6c439f3e44 --- /dev/null +++ b/crates/starknet_api/src/block_hash/receipt_commitment.rs @@ -0,0 +1,99 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::StarkHash; + +use super::block_hash_calculator::TransactionHashingData; +use crate::core::ReceiptCommitment; +use crate::crypto::patricia_hash::calculate_root; +use crate::crypto::utils::HashChain; +use crate::hash::starknet_keccak_hash; +use crate::transaction::{ + ExecutionResources, + MessageToL1, + TransactionExecutionStatus, + TransactionHash, + TransactionOutputCommon, +}; + +#[cfg(test)] +#[path = "receipt_commitment_test.rs"] +mod receipt_commitment_test; + +// The elements used to calculate a leaf in the transactions Patricia tree. +#[derive(Clone)] +pub struct ReceiptElement { + pub transaction_hash: TransactionHash, + pub transaction_output: TransactionOutputCommon, +} + +impl From<&TransactionHashingData> for ReceiptElement { + fn from(transaction_data: &TransactionHashingData) -> Self { + Self { + transaction_hash: transaction_data.transaction_hash, + transaction_output: transaction_data.transaction_output.clone(), + } + } +} + +/// Returns the root of a Patricia tree where each leaf is a receipt hash. +pub fn calculate_receipt_commitment( + receipt_elements: &[ReceiptElement], +) -> ReceiptCommitment { + ReceiptCommitment(calculate_root::( + receipt_elements.iter().map(calculate_receipt_hash).collect(), + )) +} + +// Poseidon( +// transaction hash, amount of fee paid, hash of messages sent, revert reason, +// execution resources +// ). +fn calculate_receipt_hash(receipt_element: &ReceiptElement) -> Felt { + let hash_chain = HashChain::new() + .chain(&receipt_element.transaction_hash) + .chain(&receipt_element.transaction_output.actual_fee.0.into()) + .chain(&calculate_messages_sent_hash(&receipt_element.transaction_output.messages_sent)) + .chain(&get_revert_reason_hash(&receipt_element.transaction_output.execution_status)); + chain_execution_resources(hash_chain, &receipt_element.transaction_output.execution_resources) + .get_poseidon_hash() +} + +// Poseidon( +// num_messages_sent, +// from_address_0, to_address_0, payload_length_0, payload_0, +// from_address_1, to_address_1, payload_length_1, payload_1, ... +// ). +fn calculate_messages_sent_hash(messages_sent: &Vec) -> Felt { + let mut messages_hash_chain = HashChain::new().chain(&messages_sent.len().into()); + for message_sent in messages_sent { + messages_hash_chain = messages_hash_chain + .chain(&message_sent.from_address) + .chain(&message_sent.to_address.into()) + .chain_size_and_elements(&message_sent.payload.0); + } + messages_hash_chain.get_poseidon_hash() +} + +// Returns starknet-keccak of the revert reason ASCII string, or 0 if the transaction succeeded. +fn get_revert_reason_hash(execution_status: &TransactionExecutionStatus) -> Felt { + match execution_status { + TransactionExecutionStatus::Succeeded => Felt::ZERO, + TransactionExecutionStatus::Reverted(reason) => { + starknet_keccak_hash(reason.revert_reason.as_bytes()) + } + } +} + +// Chains: +// L2 gas consumed (In the current RPC: always 0), +// L1 gas consumed (In the current RPC: +// L1 gas consumed for calldata + L1 gas consumed for steps and builtins. +// L1 data gas consumed (In the current RPC: L1 data gas consumed for blob). +fn chain_execution_resources( + hash_chain: HashChain, + execution_resources: &ExecutionResources, +) -> HashChain { + hash_chain + .chain(&Felt::ZERO) // L2 gas consumed + .chain(&execution_resources.gas_consumed.l1_gas.into()) + .chain(&execution_resources.gas_consumed.l1_data_gas.into()) +} diff --git a/crates/starknet_api/src/block_hash/receipt_commitment_test.rs b/crates/starknet_api/src/block_hash/receipt_commitment_test.rs new file mode 100644 index 00000000000..d69cedb8caa --- /dev/null +++ b/crates/starknet_api/src/block_hash/receipt_commitment_test.rs @@ -0,0 +1,55 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::Poseidon; + +use super::calculate_messages_sent_hash; +use crate::block_hash::receipt_commitment::{ + calculate_receipt_commitment, + calculate_receipt_hash, + get_revert_reason_hash, + ReceiptElement, +}; +use crate::block_hash::test_utils::{generate_message_to_l1, get_transaction_output}; +use crate::core::ReceiptCommitment; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; +use crate::transaction::{ + RevertedTransactionExecutionStatus, + TransactionExecutionStatus, + TransactionHash, +}; + +#[test] +fn test_receipt_hash_regression() { + let transaction_receipt = ReceiptElement { + transaction_hash: TransactionHash(Felt::from(1234_u16)), + transaction_output: get_transaction_output(), + }; + + let expected_hash = felt!("0x6276abf21e7c68b2eecfdc8a845b11b44401901f5f040efe10c60d625049646"); + assert_eq!(calculate_receipt_hash(&transaction_receipt), expected_hash); + + let expected_root = ReceiptCommitment(felt!( + "0x31963cb891ebb825e83514deb748c89b6967b5368cbc48a9b56193a1464ca87" + )); + assert_eq!(calculate_receipt_commitment::(&[transaction_receipt]), expected_root); +} + +#[test] +fn test_messages_sent_regression() { + let messages_sent = vec![generate_message_to_l1(0), generate_message_to_l1(1)]; + let messages_hash = calculate_messages_sent_hash(&messages_sent); + let expected_hash = felt!("0x00c89474a9007dc060aed76caf8b30b927cfea1ebce2d134b943b8d7121004e4"); + assert_eq!(messages_hash, expected_hash); +} + +#[test] +fn test_revert_reason_hash_regression() { + let execution_succeeded = TransactionExecutionStatus::Succeeded; + assert_eq!(get_revert_reason_hash(&execution_succeeded), Felt::ZERO); + let execution_reverted = + TransactionExecutionStatus::Reverted(RevertedTransactionExecutionStatus { + revert_reason: "ABC".to_string(), + }); + let expected_hash = felt!("0x01629b9dda060bb30c7908346f6af189c16773fa148d3366701fbaa35d54f3c8"); + assert_eq!(get_revert_reason_hash(&execution_reverted), expected_hash); +} diff --git a/crates/starknet_api/src/block_hash/state_diff_hash.rs b/crates/starknet_api/src/block_hash/state_diff_hash.rs new file mode 100644 index 00000000000..d588992bfda --- /dev/null +++ b/crates/starknet_api/src/block_hash/state_diff_hash.rs @@ -0,0 +1,123 @@ +use indexmap::IndexMap; +use once_cell::sync::Lazy; +use starknet_types_core::felt::Felt; + +use crate::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, StateDiffCommitment}; +use crate::crypto::utils::HashChain; +use crate::hash::PoseidonHash; +use crate::state::{StorageKey, ThinStateDiff}; +use crate::transaction_hash::ascii_as_felt; + +#[cfg(test)] +#[path = "state_diff_hash_test.rs"] +mod state_diff_hash_test; + +static STARKNET_STATE_DIFF0: Lazy = Lazy::new(|| { + ascii_as_felt("STARKNET_STATE_DIFF0").expect("ascii_as_felt failed for 'STARKNET_STATE_DIFF0'") +}); + +/// Poseidon( +/// "STARKNET_STATE_DIFF0", deployed_contracts, declared_classes, deprecated_declared_classes, +/// 1, 0, storage_diffs, nonces +/// ). +pub fn calculate_state_diff_hash(state_diff: &ThinStateDiff) -> StateDiffCommitment { + let mut hash_chain = HashChain::new(); + hash_chain = hash_chain.chain(&STARKNET_STATE_DIFF0); + hash_chain = chain_updated_contracts( + &state_diff.deployed_contracts, + &state_diff.replaced_classes, + hash_chain, + ); + hash_chain = chain_declared_classes(&state_diff.declared_classes, hash_chain); + hash_chain = + chain_deprecated_declared_classes(&state_diff.deprecated_declared_classes, hash_chain); + hash_chain = hash_chain.chain(&Felt::ONE) // placeholder. + .chain(&Felt::ZERO); // placeholder. + hash_chain = chain_storage_diffs(&state_diff.storage_diffs, hash_chain); + hash_chain = chain_nonces(&state_diff.nonces, hash_chain); + StateDiffCommitment(PoseidonHash(hash_chain.get_poseidon_hash())) +} + +// Chains: [number_of_updated_contracts, address_0, class_hash_0, address_1, class_hash_1, ...]. +// The updated contracts includes deployed contracts and replaced classes. +fn chain_updated_contracts( + deployed_contracts: &IndexMap, + replaced_classes: &IndexMap, + mut hash_chain: HashChain, +) -> HashChain { + let updated_contracts = deployed_contracts.iter().chain(replaced_classes.iter()); + hash_chain = hash_chain.chain(&(deployed_contracts.len() + replaced_classes.len()).into()); + for (address, class_hash) in sorted_index_map(&updated_contracts.collect()) { + hash_chain = hash_chain.chain(&address.0).chain(class_hash); + } + hash_chain +} + +// Chains: [number_of_declared_classes, +// class_hash_0, compiled_class_hash_0, class_hash_1, compiled_class_hash_1, ...]. +fn chain_declared_classes( + declared_classes: &IndexMap, + mut hash_chain: HashChain, +) -> HashChain { + hash_chain = hash_chain.chain(&declared_classes.len().into()); + for (class_hash, compiled_class_hash) in sorted_index_map(declared_classes) { + hash_chain = hash_chain.chain(&class_hash).chain(&compiled_class_hash.0) + } + hash_chain +} + +// Chains: [number_of_old_declared_classes, class_hash_0, class_hash_1, ...]. +fn chain_deprecated_declared_classes( + deprecated_declared_classes: &[ClassHash], + hash_chain: HashChain, +) -> HashChain { + let mut sorted_deprecated_declared_classes = deprecated_declared_classes.to_vec(); + sorted_deprecated_declared_classes.sort_unstable(); + hash_chain + .chain(&sorted_deprecated_declared_classes.len().into()) + .chain_iter(sorted_deprecated_declared_classes.iter().map(|class_hash| &class_hash.0)) +} + +// Chains: [number_of_updated_contracts, +// contract_address_0, number_of_updates_in_contract_0, key_0, value0, key1, value1, ..., +// contract_address_1, number_of_updates_in_contract_1, key_0, value0, key1, value1, ..., +// ] +fn chain_storage_diffs( + storage_diffs: &IndexMap>, + hash_chain: HashChain, +) -> HashChain { + let mut n_updated_contracts = 0_u64; + let mut storage_diffs_chain = HashChain::new(); + for (contract_address, key_value_map) in sorted_index_map(storage_diffs) { + if key_value_map.is_empty() { + // Filter out a contract with empty storage maps. + continue; + } + n_updated_contracts += 1; + storage_diffs_chain = storage_diffs_chain.chain(&contract_address); + storage_diffs_chain = storage_diffs_chain.chain(&key_value_map.len().into()); + for (key, value) in sorted_index_map(&key_value_map) { + storage_diffs_chain = storage_diffs_chain.chain(&key).chain(&value); + } + } + hash_chain.chain(&n_updated_contracts.into()).extend(storage_diffs_chain) +} + +// Chains: [number_of_updated_contracts nonces, +// contract_address_0, nonce_0, contract_address_1, nonce_1, ..., +// ] +fn chain_nonces(nonces: &IndexMap, mut hash_chain: HashChain) -> HashChain { + hash_chain = hash_chain.chain(&nonces.len().into()); + for (contract_address, nonce) in sorted_index_map(nonces) { + hash_chain = hash_chain.chain(&contract_address); + hash_chain = hash_chain.chain(&nonce); + } + hash_chain +} + +// Returns a clone of the map, sorted by keys. +fn sorted_index_map(map: &IndexMap) -> IndexMap { + let mut sorted_map = map.clone(); + sorted_map.sort_unstable_keys(); + sorted_map +} diff --git a/crates/starknet_api/src/block_hash/state_diff_hash_test.rs b/crates/starknet_api/src/block_hash/state_diff_hash_test.rs new file mode 100644 index 00000000000..f55819dfbaf --- /dev/null +++ b/crates/starknet_api/src/block_hash/state_diff_hash_test.rs @@ -0,0 +1,140 @@ +use indexmap::indexmap; + +use crate::block_hash::state_diff_hash::{ + calculate_state_diff_hash, + chain_declared_classes, + chain_deprecated_declared_classes, + chain_nonces, + chain_storage_diffs, + chain_updated_contracts, +}; +use crate::block_hash::test_utils::get_state_diff; +use crate::core::{ClassHash, CompiledClassHash, Nonce, StateDiffCommitment}; +use crate::crypto::utils::HashChain; +use crate::felt; +use crate::hash::{FeltConverter, PoseidonHash, TryIntoFelt}; + +#[test] +fn test_state_diff_hash_regression() { + let state_diff = get_state_diff(); + + let expected_hash = StateDiffCommitment(PoseidonHash(felt!( + "0x0281f5966e49ad7dad9323826d53d1d27c0c4e6ebe5525e2e2fbca549bfa0a67" + ))); + + assert_eq!(expected_hash, calculate_state_diff_hash(&state_diff)); +} + +#[test] +fn test_sorting_deployed_contracts() { + let deployed_contracts_0 = indexmap! { + 0u64.into() => ClassHash(3u64.into()), + 1u64.into() => ClassHash(2u64.into()), + }; + let replaced_classes_0 = indexmap! { + 2u64.into() => ClassHash(1u64.into()), + }; + let deployed_contracts_1 = indexmap! { + 2u64.into() => ClassHash(1u64.into()), + 0u64.into() => ClassHash(3u64.into()), + }; + let replaced_classes_1 = indexmap! { + 1u64.into() => ClassHash(2u64.into()), + }; + assert_eq!( + chain_updated_contracts(&deployed_contracts_0, &replaced_classes_0, HashChain::new()) + .get_poseidon_hash(), + chain_updated_contracts(&deployed_contracts_1, &replaced_classes_1, HashChain::new()) + .get_poseidon_hash(), + ); +} + +#[test] +fn test_sorting_declared_classes() { + let declared_classes_0 = indexmap! { + ClassHash(0u64.into()) => CompiledClassHash(3u64.into()), + ClassHash(1u64.into()) => CompiledClassHash(2u64.into()), + }; + let declared_classes_1 = indexmap! { + ClassHash(1u64.into()) => CompiledClassHash(2u64.into()), + ClassHash(0u64.into()) => CompiledClassHash(3u64.into()), + }; + assert_eq!( + chain_declared_classes(&declared_classes_0, HashChain::new()).get_poseidon_hash(), + chain_declared_classes(&declared_classes_1, HashChain::new()).get_poseidon_hash(), + ); +} + +#[test] +fn test_sorting_deprecated_declared_classes() { + let deprecated_declared_classes_0 = vec![ClassHash(0u64.into()), ClassHash(1u64.into())]; + let deprecated_declared_classes_1 = vec![ClassHash(1u64.into()), ClassHash(0u64.into())]; + assert_eq!( + chain_deprecated_declared_classes(&deprecated_declared_classes_0, HashChain::new()) + .get_poseidon_hash(), + chain_deprecated_declared_classes(&deprecated_declared_classes_1, HashChain::new()) + .get_poseidon_hash(), + ); +} + +#[test] +fn test_sorting_storage_diffs() { + let storage_diffs_0 = indexmap! { + 0u64.into() => indexmap! { + 1u64.into() => 2u64.into(), + 3u64.into() => 4u64.into(), + }, + 5u64.into() => indexmap! { + 6u64.into() => 7u64.into(), + }, + }; + let storage_diffs_1 = indexmap! { + 5u64.into() => indexmap! { + 6u64.into() => 7u64.into(), + }, + 0u64.into() => indexmap! { + 3u64.into() => 4u64.into(), + 1u64.into() => 2u64.into(), + }, + }; + assert_eq!( + chain_storage_diffs(&storage_diffs_0, HashChain::new()).get_poseidon_hash(), + chain_storage_diffs(&storage_diffs_1, HashChain::new()).get_poseidon_hash(), + ); +} + +#[test] +fn test_empty_storage_diffs() { + let storage_diffs_0 = indexmap! { + 0u64.into() => indexmap! { + 1u64.into() => 2u64.into(), + }, + 3u64.into() => indexmap! { + }, + }; + let storage_diffs_1 = indexmap! { + 0u64.into() => indexmap! { + 1u64.into() => 2u64.into(), + }, + }; + assert_eq!( + chain_storage_diffs(&storage_diffs_0, HashChain::new()).get_poseidon_hash(), + chain_storage_diffs(&storage_diffs_1, HashChain::new()).get_poseidon_hash(), + ); +} + +#[test] +fn test_sorting_nonces() { + let nonces_0 = indexmap! { + 0u64.into() => Nonce(3u64.into()), + 1u64.into() => Nonce(2u64.into()), + }; + let nonces_1 = indexmap! { + 1u64.into() => Nonce(2u64.into()), + 0u64.into() => Nonce(3u64.into()), + }; + assert_eq!( + chain_nonces(&nonces_0, HashChain::new()).get_poseidon_hash(), + chain_nonces(&nonces_1, HashChain::new()).get_poseidon_hash(), + ); +} diff --git a/crates/starknet_api/src/block_hash/test_utils.rs b/crates/starknet_api/src/block_hash/test_utils.rs new file mode 100644 index 00000000000..c433c661d92 --- /dev/null +++ b/crates/starknet_api/src/block_hash/test_utils.rs @@ -0,0 +1,77 @@ +use std::collections::HashMap; + +use indexmap::indexmap; +use primitive_types::H160; +use starknet_types_core::felt::Felt; + +use crate::core::{ClassHash, CompiledClassHash, ContractAddress, EthAddress, Nonce}; +use crate::state::ThinStateDiff; +use crate::transaction::{ + Builtin, + ExecutionResources, + Fee, + GasVector, + L2ToL1Payload, + MessageToL1, + RevertedTransactionExecutionStatus, + TransactionExecutionStatus, + TransactionOutputCommon, +}; + +pub(crate) fn get_transaction_output() -> TransactionOutputCommon { + let execution_status = + TransactionExecutionStatus::Reverted(RevertedTransactionExecutionStatus { + revert_reason: "aborted".to_string(), + }); + let execution_resources = ExecutionResources { + steps: 98, + builtin_instance_counter: HashMap::from([(Builtin::Bitwise, 11), (Builtin::EcOp, 22)]), + memory_holes: 76, + da_gas_consumed: GasVector { l1_gas: 54, l1_data_gas: 10 }, + gas_consumed: GasVector { l1_gas: 16580, l1_data_gas: 32 }, + }; + TransactionOutputCommon { + actual_fee: Fee(99804), + messages_sent: vec![generate_message_to_l1(34), generate_message_to_l1(56)], + events: vec![], + execution_status, + execution_resources, + } +} + +pub(crate) fn generate_message_to_l1(seed: u64) -> MessageToL1 { + MessageToL1 { + from_address: ContractAddress::from(seed), + to_address: EthAddress(H160::from_low_u64_be(seed + 1)), + payload: L2ToL1Payload(vec![Felt::from(seed + 2), Felt::from(seed + 3)]), + } +} + +pub(crate) fn get_state_diff() -> ThinStateDiff { + ThinStateDiff { + deployed_contracts: indexmap! { + 0u64.into() => ClassHash(1u64.into()), + 2u64.into() => ClassHash(3u64.into()), + }, + storage_diffs: indexmap! { + 4u64.into() => indexmap! { + 5u64.into() => 6u64.into(), + 7u64.into() => 8u64.into(), + }, + 9u64.into() => indexmap! { + 10u64.into() => 11u64.into(), + }, + }, + declared_classes: indexmap! { + ClassHash(12u64.into()) => CompiledClassHash(13u64.into()), + ClassHash(14u64.into()) => CompiledClassHash(15u64.into()), + }, + deprecated_declared_classes: vec![ClassHash(16u64.into())], + nonces: indexmap! { + 17u64.into() => Nonce(18u64.into()), + }, + replaced_classes: indexmap! { + 19u64.into() => ClassHash(20u64.into()), + }, + } +} diff --git a/crates/starknet_api/src/block_hash/transaction_commitment.rs b/crates/starknet_api/src/block_hash/transaction_commitment.rs new file mode 100644 index 00000000000..11a1a401908 --- /dev/null +++ b/crates/starknet_api/src/block_hash/transaction_commitment.rs @@ -0,0 +1,53 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::StarkHash as CoreStarkHash; + +use super::block_hash_calculator::TransactionHashingData; +use crate::core::TransactionCommitment; +use crate::crypto::patricia_hash::calculate_root; +use crate::crypto::utils::HashChain; +use crate::transaction::{TransactionHash, TransactionSignature}; + +#[cfg(test)] +#[path = "transaction_commitment_test.rs"] +mod transaction_commitment_test; + +/// The elements used to calculate a leaf in the transactions Patricia tree. +#[derive(Clone)] +pub struct TransactionLeafElement { + pub(crate) transaction_hash: TransactionHash, + pub(crate) transaction_signature: Option, +} + +impl From<&TransactionHashingData> for TransactionLeafElement { + fn from(transaction_data: &TransactionHashingData) -> Self { + Self { + transaction_hash: transaction_data.transaction_hash, + transaction_signature: transaction_data.transaction_signature.clone(), + } + } +} + +/// Returns the root of a Patricia tree where each leaf is +/// H(transaction_hash, transaction_signature). +/// The leaf of a transaction types without a signature field is: H(transaction_hash, 0). +pub fn calculate_transactions_commitment( + transaction_leaf_elements: &[TransactionLeafElement], +) -> TransactionCommitment { + let transaction_leaves = + transaction_leaf_elements.iter().map(calculate_transaction_leaf).collect(); + TransactionCommitment(calculate_root::(transaction_leaves)) +} + +fn calculate_transaction_leaf(transaction_leaf_elements: &TransactionLeafElement) -> Felt { + HashChain::new() + .chain(&transaction_leaf_elements.transaction_hash.0) + .chain_iter( + transaction_leaf_elements + .transaction_signature + .as_ref() + .unwrap_or(&TransactionSignature(vec![Felt::ZERO])) + .0 + .iter(), + ) + .get_poseidon_hash() +} diff --git a/crates/starknet_api/src/block_hash/transaction_commitment_test.rs b/crates/starknet_api/src/block_hash/transaction_commitment_test.rs new file mode 100644 index 00000000000..29e7ce977f1 --- /dev/null +++ b/crates/starknet_api/src/block_hash/transaction_commitment_test.rs @@ -0,0 +1,51 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::Poseidon; + +use super::TransactionLeafElement; +use crate::block_hash::transaction_commitment::{ + calculate_transaction_leaf, + calculate_transactions_commitment, +}; +use crate::core::TransactionCommitment; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; +use crate::transaction::{TransactionHash, TransactionSignature}; + +#[test] +fn test_transaction_leaf_regression() { + let transaction_leaf_elements = get_transaction_leaf_element(); + let expected_leaf = felt!("0x2f0d8840bcf3bc629598d8a6cc80cb7c0d9e52d93dab244bbf9cd0dca0ad082"); + + assert_eq!(expected_leaf, calculate_transaction_leaf(&transaction_leaf_elements)); +} + +#[test] +fn test_transaction_leaf_without_signature_regression() { + let transaction_leaf_elements = TransactionLeafElement { + transaction_hash: TransactionHash(Felt::ONE), + transaction_signature: None, + }; + let expected_leaf = felt!("0x00a93bf5e58b9378d093aa86ddc2f61a3295a1d1e665bd0ef3384dd07b30e033"); + + assert_eq!(expected_leaf, calculate_transaction_leaf(&transaction_leaf_elements)); +} + +#[test] +fn test_transactions_commitment_regression() { + let transaction_leaf_elements = get_transaction_leaf_element(); + let expected_root = felt!("0x0282b635972328bd1cfa86496fe920d20bd9440cd78ee8dc90ae2b383d664dcf"); + + assert_eq!( + TransactionCommitment(expected_root), + calculate_transactions_commitment::(&[ + transaction_leaf_elements.clone(), + transaction_leaf_elements + ]) + ); +} + +fn get_transaction_leaf_element() -> TransactionLeafElement { + let transaction_hash = TransactionHash(Felt::ONE); + let transaction_signature = TransactionSignature(vec![Felt::TWO, Felt::THREE]); + TransactionLeafElement { transaction_hash, transaction_signature: Some(transaction_signature) } +} diff --git a/crates/starknet-api/src/block_test.rs b/crates/starknet_api/src/block_test.rs similarity index 63% rename from crates/starknet-api/src/block_test.rs rename to crates/starknet_api/src/block_test.rs index e5cac76eb41..0705918cb1c 100644 --- a/crates/starknet-api/src/block_test.rs +++ b/crates/starknet_api/src/block_test.rs @@ -1,9 +1,9 @@ use super::verify_block_signature; use crate::block::{BlockHash, BlockNumber, BlockSignature}; use crate::core::{GlobalRoot, SequencerPublicKey}; -use crate::crypto::{PublicKey, Signature}; -use crate::hash::StarkFelt; -use crate::stark_felt; +use crate::crypto::utils::{PublicKey, Signature}; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; #[test] fn test_block_number_iteration() { @@ -30,15 +30,14 @@ fn test_block_number_iteration() { fn block_signature_verification() { // Values taken from Mainnet. let block_hash = - BlockHash(stark_felt!("0x7d5db04c5ca2aea828180dc441afb1580e3cee7547a3567ced3aa5bb8b273c0")); - let state_commitment = GlobalRoot(stark_felt!( - "0x64689c12248e1110af4b3af0e2b43cd51ad13e8855f10e37669e2a4baf919c6" - )); + BlockHash(felt!("0x7d5db04c5ca2aea828180dc441afb1580e3cee7547a3567ced3aa5bb8b273c0")); + let state_commitment = + GlobalRoot(felt!("0x64689c12248e1110af4b3af0e2b43cd51ad13e8855f10e37669e2a4baf919c6")); let signature = BlockSignature(Signature { - r: stark_felt!("0x1b382bbfd693011c9b7692bc932b23ed9c288deb27c8e75772e172abbe5950c"), - s: stark_felt!("0xbe4438085057e1a7c704a0da3b30f7b8340fe3d24c86772abfd24aa597e42"), + r: felt!("0x1b382bbfd693011c9b7692bc932b23ed9c288deb27c8e75772e172abbe5950c"), + s: felt!("0xbe4438085057e1a7c704a0da3b30f7b8340fe3d24c86772abfd24aa597e42"), }); - let sequencer_pub_key = SequencerPublicKey(PublicKey(stark_felt!( + let sequencer_pub_key = SequencerPublicKey(PublicKey(felt!( "0x48253ff2c3bed7af18bde0b611b083b39445959102d4947c51c4db6aa4f4e58" ))); diff --git a/crates/starknet-api/src/core.rs b/crates/starknet_api/src/core.rs similarity index 70% rename from crates/starknet-api/src/core.rs rename to crates/starknet_api/src/core.rs index f914d7eb7f0..d3349e0c591 100644 --- a/crates/starknet-api/src/core.rs +++ b/crates/starknet_api/src/core.rs @@ -2,27 +2,73 @@ #[path = "core_test.rs"] mod core_test; +use core::fmt::Display; use std::fmt::Debug; use derive_more::Display; use once_cell::sync::Lazy; use primitive_types::H160; -use serde::{Deserialize, Serialize}; -use starknet_crypto::FieldElement; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use starknet_types_core::felt::{Felt, NonZeroFelt}; +use starknet_types_core::hash::{Pedersen, StarkHash as CoreStarkHash}; -use crate::crypto::PublicKey; -use crate::hash::{pedersen_hash_array, PoseidonHash, StarkFelt, StarkHash}; +use crate::crypto::utils::PublicKey; +use crate::hash::{PoseidonHash, StarkHash}; use crate::serde_utils::{BytesAsHex, PrefixedBytesAsHex}; use crate::transaction::{Calldata, ContractAddressSalt}; use crate::{impl_from_through_intermediate, StarknetApiError}; /// A chain id. -#[derive(Clone, Debug, Display, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct ChainId(pub String); +#[derive(Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] +pub enum ChainId { + Mainnet, + Sepolia, + IntegrationSepolia, + Other(String), +} + +impl Serialize for ChainId { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for ChainId { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Ok(ChainId::from(s)) + } +} +impl From for ChainId { + fn from(s: String) -> Self { + match s.as_ref() { + "SN_MAIN" => ChainId::Mainnet, + "SN_SEPOLIA" => ChainId::Sepolia, + "SN_INTEGRATION_SEPOLIA" => ChainId::IntegrationSepolia, + other => ChainId::Other(other.to_owned()), + } + } +} +impl Display for ChainId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ChainId::Mainnet => write!(f, "SN_MAIN"), + ChainId::Sepolia => write!(f, "SN_SEPOLIA"), + ChainId::IntegrationSepolia => write!(f, "SN_INTEGRATION_SEPOLIA"), + ChainId::Other(ref s) => write!(f, "{}", s), + } + } +} impl ChainId { pub fn as_hex(&self) -> String { - format!("0x{}", hex::encode(&self.0)) + format!("0x{}", hex::encode(self.to_string())) } } @@ -49,8 +95,8 @@ pub const BLOCK_HASH_TABLE_ADDRESS: ContractAddress = ContractAddress(PatriciaKe )] pub struct ContractAddress(pub PatriciaKey); -impl From for StarkFelt { - fn from(contract_address: ContractAddress) -> StarkFelt { +impl From for Felt { + fn from(contract_address: ContractAddress) -> Felt { **contract_address } } @@ -68,13 +114,10 @@ pub const MAX_STORAGE_ITEM_SIZE: u16 = 256; /// The prefix used in the calculation of a contract address. pub const CONTRACT_ADDRESS_PREFIX: &str = "STARKNET_CONTRACT_ADDRESS"; /// The size of the contract address domain. -pub static CONTRACT_ADDRESS_DOMAIN_SIZE: Lazy = Lazy::new(|| { - StarkFelt::try_from(PATRICIA_KEY_UPPER_BOUND) - .unwrap_or_else(|_| panic!("Failed to convert {PATRICIA_KEY_UPPER_BOUND} to StarkFelt")) -}); +pub const CONTRACT_ADDRESS_DOMAIN_SIZE: Felt = Felt::from_hex_unchecked(PATRICIA_KEY_UPPER_BOUND); /// The address upper bound; it is defined to be congruent with the storage var address upper bound. -pub static L2_ADDRESS_UPPER_BOUND: Lazy = Lazy::new(|| { - FieldElement::from(*CONTRACT_ADDRESS_DOMAIN_SIZE) - FieldElement::from(MAX_STORAGE_ITEM_SIZE) +pub static L2_ADDRESS_UPPER_BOUND: Lazy = Lazy::new(|| { + NonZeroFelt::try_from(CONTRACT_ADDRESS_DOMAIN_SIZE - Felt::from(MAX_STORAGE_ITEM_SIZE)).unwrap() }); impl TryFrom for ContractAddress { @@ -91,18 +134,20 @@ pub fn calculate_contract_address( constructor_calldata: &Calldata, deployer_address: ContractAddress, ) -> Result { - let constructor_calldata_hash = pedersen_hash_array(&constructor_calldata.0); + let constructor_calldata_hash = Pedersen::hash_array(&constructor_calldata.0); let contract_address_prefix = format!("0x{}", hex::encode(CONTRACT_ADDRESS_PREFIX)); - let mut address = FieldElement::from(pedersen_hash_array(&[ - StarkFelt::try_from(contract_address_prefix.as_str())?, + let address = Pedersen::hash_array(&[ + Felt::from_hex(contract_address_prefix.as_str()).map_err(|_| { + StarknetApiError::OutOfRange { string: contract_address_prefix.clone() } + })?, *deployer_address.0.key(), salt.0, class_hash.0, constructor_calldata_hash, - ])); - address = address % *L2_ADDRESS_UPPER_BOUND; + ]); + let (_, address) = address.div_rem(&L2_ADDRESS_UPPER_BOUND); - ContractAddress::try_from(StarkFelt::from(address)) + ContractAddress::try_from(address) } /// The hash of a ContractClass. @@ -155,17 +200,16 @@ pub struct CompiledClassHash(pub StarkHash); Ord, derive_more::Deref, )] -pub struct Nonce(pub StarkFelt); +pub struct Nonce(pub Felt); impl Nonce { pub fn try_increment(&self) -> Result { - let current_nonce = FieldElement::from(self.0); - // Check if an overflow occurred during increment. - match StarkFelt::from(current_nonce + FieldElement::ONE) { - StarkFelt::ZERO => Err(StarknetApiError::OutOfRange { string: format!("{:?}", self) }), - incremented_felt => Ok(Self(incremented_felt)), + let incremented = self.0 + Felt::ONE; + if incremented == Felt::ZERO { + return Err(StarknetApiError::OutOfRange { string: format!("{:?}", self) }); } + Ok(Self(incremented)) } } @@ -244,9 +288,7 @@ pub struct EventCommitment(pub StarkHash); )] pub struct ReceiptCommitment(pub StarkHash); -#[derive( - Debug, Copy, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, -)] +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub struct StateDiffCommitment(pub PoseidonHash); /// A key for nodes of a Patricia tree. @@ -278,7 +320,7 @@ impl PatriciaKey { impl From for PatriciaKey { fn from(val: u128) -> Self { - PatriciaKey::try_from(StarkFelt::from(val)).expect("Failed to convert u128 to PatriciaKey.") + PatriciaKey::try_from(Felt::from(val)).expect("Failed to convert u128 to PatriciaKey.") } } @@ -288,7 +330,7 @@ impl TryFrom for PatriciaKey { type Error = StarknetApiError; fn try_from(value: StarkHash) -> Result { - if value < *CONTRACT_ADDRESS_DOMAIN_SIZE { + if value < CONTRACT_ADDRESS_DOMAIN_SIZE { return Ok(PatriciaKey(value)); } Err(StarknetApiError::OutOfRange { string: format!("[0x0, {PATRICIA_KEY_UPPER_BOUND})") }) @@ -306,7 +348,7 @@ impl Debug for PatriciaKey { #[macro_export] macro_rules! patricia_key { ($s:expr) => { - PatriciaKey::try_from(StarkHash::try_from($s).unwrap()).unwrap() + PatriciaKey::try_from(felt!($s)).unwrap() }; } @@ -315,7 +357,7 @@ macro_rules! patricia_key { #[macro_export] macro_rules! class_hash { ($s:expr) => { - ClassHash(StarkHash::try_from($s).unwrap()) + ClassHash(felt!($s)) }; } @@ -336,12 +378,13 @@ macro_rules! contract_address { #[serde(try_from = "PrefixedBytesAsHex<20_usize>", into = "PrefixedBytesAsHex<20_usize>")] pub struct EthAddress(pub H160); -impl TryFrom for EthAddress { +impl TryFrom for EthAddress { type Error = StarknetApiError; - fn try_from(felt: StarkFelt) -> Result { - const COMPLIMENT_OF_H160: usize = std::mem::size_of::() - H160::len_bytes(); + fn try_from(felt: Felt) -> Result { + const COMPLIMENT_OF_H160: usize = std::mem::size_of::() - H160::len_bytes(); - let (rest, h160_bytes) = felt.bytes().split_at(COMPLIMENT_OF_H160); + let bytes = felt.to_bytes_be(); + let (rest, h160_bytes) = bytes.split_at(COMPLIMENT_OF_H160); if rest != [0u8; COMPLIMENT_OF_H160] { return Err(StarknetApiError::OutOfRange { string: felt.to_string() }); } @@ -350,12 +393,9 @@ impl TryFrom for EthAddress { } } -impl From for StarkFelt { +impl From for Felt { fn from(value: EthAddress) -> Self { - let mut bytes = [0u8; 32]; - // Padding H160 with zeros to 32 bytes (big endian) - bytes[12..32].copy_from_slice(value.0.as_bytes()); - StarkFelt::new_unchecked(bytes) + Felt::from_bytes_be_slice(value.0.as_bytes()) } } diff --git a/crates/starknet-api/src/core_test.rs b/crates/starknet_api/src/core_test.rs similarity index 58% rename from crates/starknet-api/src/core_test.rs rename to crates/starknet_api/src/core_test.rs index b330cf36d81..c2b801d6d65 100644 --- a/crates/starknet-api/src/core_test.rs +++ b/crates/starknet_api/src/core_test.rs @@ -1,5 +1,6 @@ use assert_matches::assert_matches; -use starknet_crypto::FieldElement; +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::{Pedersen, StarkHash as CoreStarkHash}; use crate::core::{ calculate_contract_address, @@ -12,13 +13,13 @@ use crate::core::{ CONTRACT_ADDRESS_PREFIX, L2_ADDRESS_UPPER_BOUND, }; -use crate::hash::{pedersen_hash_array, StarkFelt, StarkHash}; +use crate::hash::{FeltConverter, StarkHash, TryIntoFelt}; use crate::transaction::{Calldata, ContractAddressSalt}; -use crate::{class_hash, patricia_key, stark_felt}; +use crate::{class_hash, felt, patricia_key}; #[test] fn patricia_key_valid() { - let hash = stark_felt!("0x123"); + let hash = felt!("0x123"); let patricia_key = PatriciaKey::try_from(hash).unwrap(); assert_eq!(patricia_key.0, hash); } @@ -26,7 +27,7 @@ fn patricia_key_valid() { #[test] fn patricia_key_out_of_range() { // 2**251 - let hash = stark_felt!("0x800000000000000000000000000000000000000000000000000000000000000"); + let hash = felt!("0x800000000000000000000000000000000000000000000000000000000000000"); let err = PatriciaKey::try_from(hash); assert_matches!(err, Err(StarknetApiError::OutOfRange { string: _err_str })); } @@ -35,47 +36,43 @@ fn patricia_key_out_of_range() { fn patricia_key_macro() { assert_eq!( patricia_key!("0x123"), - PatriciaKey::try_from( - StarkHash::new([ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0x1, 0x23 - ]) - .unwrap() - ) + PatriciaKey::try_from(StarkHash::from_bytes_be(&[ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0x1, 0x23 + ])) .unwrap() ); } #[test] fn test_calculate_contract_address() { - let salt = ContractAddressSalt(stark_felt!(1337_u16)); + let salt = ContractAddressSalt(Felt::from(1337_u16)); let class_hash = class_hash!("0x110"); let deployer_address = ContractAddress::default(); let constructor_calldata = - Calldata(vec![stark_felt!(60_u16), stark_felt!(70_u16), FieldElement::MAX.into()].into()); + Calldata(vec![Felt::from(60_u16), Felt::from(70_u16), Felt::MAX].into()); let actual_address = calculate_contract_address(salt, class_hash, &constructor_calldata, deployer_address) .unwrap(); - let constructor_calldata_hash = pedersen_hash_array(&constructor_calldata.0); - let address = pedersen_hash_array(&[ - StarkFelt::try_from(format!("0x{}", hex::encode(CONTRACT_ADDRESS_PREFIX)).as_str()) - .unwrap(), + let constructor_calldata_hash = Pedersen::hash_array(&constructor_calldata.0); + let address = Pedersen::hash_array(&[ + Felt::from_hex_unchecked(format!("0x{}", hex::encode(CONTRACT_ADDRESS_PREFIX)).as_str()), *deployer_address.0.key(), salt.0, class_hash.0, constructor_calldata_hash, ]); - let mod_address = FieldElement::from(address) % *L2_ADDRESS_UPPER_BOUND; - let expected_address = ContractAddress::try_from(StarkFelt::from(mod_address)).unwrap(); + let (_, mod_address) = address.div_rem(&L2_ADDRESS_UPPER_BOUND); + let expected_address = ContractAddress::try_from(mod_address).unwrap(); assert_eq!(actual_address, expected_address); } #[test] fn eth_address_serde() { - let eth_address = EthAddress::try_from(StarkFelt::try_from("0x001").unwrap()).unwrap(); + let eth_address = EthAddress::try_from(felt!("0x001")).unwrap(); let serialized = serde_json::to_string(ð_address).unwrap(); assert_eq!(serialized, r#""0x1""#); @@ -86,7 +83,7 @@ fn eth_address_serde() { #[test] fn nonce_overflow() { // Increment on this value should overflow back to 0. - let max_nonce = Nonce(StarkFelt::from(FieldElement::MAX)); + let max_nonce = Nonce(Felt::MAX); let overflowed_nonce = max_nonce.try_increment(); assert_matches!(overflowed_nonce, Err(StarknetApiError::OutOfRange { string: _err_str })); diff --git a/crates/starknet_api/src/crypto.rs b/crates/starknet_api/src/crypto.rs new file mode 100644 index 00000000000..3e4100ee5c9 --- /dev/null +++ b/crates/starknet_api/src/crypto.rs @@ -0,0 +1,2 @@ +pub mod patricia_hash; +pub mod utils; diff --git a/crates/starknet_api/src/crypto/crypto_test.rs b/crates/starknet_api/src/crypto/crypto_test.rs new file mode 100644 index 00000000000..24d6042921a --- /dev/null +++ b/crates/starknet_api/src/crypto/crypto_test.rs @@ -0,0 +1,27 @@ +// Unittest for verify_message_signature + +use starknet_types_core::hash::{Poseidon, StarkHash}; + +use crate::crypto::utils::{verify_message_hash_signature, PublicKey, Signature}; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; + +#[test] +fn signature_verification() { + // The signed message of block 4256. + let message_hash = Poseidon::hash_array(&[ + felt!("0x7d5db04c5ca2aea828180dc441afb1580e3cee7547a3567ced3aa5bb8b273c0"), + felt!("0x64689c12248e1110af4b3af0e2b43cd51ad13e8855f10e37669e2a4baf919c6"), + ]); + // The signature of the message. + let signature = Signature { + r: felt!("0x1b382bbfd693011c9b7692bc932b23ed9c288deb27c8e75772e172abbe5950c"), + s: felt!("0xbe4438085057e1a7c704a0da3b30f7b8340fe3d24c86772abfd24aa597e42"), + }; + // The public key of the sequencer. + let public_key = + PublicKey(felt!("0x48253ff2c3bed7af18bde0b611b083b39445959102d4947c51c4db6aa4f4e58")); + + let result = verify_message_hash_signature(&message_hash, &signature, &public_key).unwrap(); + assert!(result); +} diff --git a/crates/starknet_api/src/crypto/patricia_hash.rs b/crates/starknet_api/src/crypto/patricia_hash.rs new file mode 100644 index 00000000000..41acf4e22f3 --- /dev/null +++ b/crates/starknet_api/src/crypto/patricia_hash.rs @@ -0,0 +1,132 @@ +//! Patricia hash tree implementation. +//! +//! Supports root hash calculation for Stark felt values, keyed by consecutive 64 bits numbers, +//! starting from 0. +//! +//! Each edge is marked with one or more bits. +//! The key of a node is the concatenation of the edges' marks in the path from the root to this +//! node. +//! The input keys are in the leaves, and each leaf is an input key. +//! +//! The edges coming out of an internal node with a key `K` are: +//! - If there are input keys that start with 'K0...' and 'K1...', then two edges come out, marked +//! with '0' and '1' bits. +//! - Otherwise, a single edge mark with 'Z' is coming out. 'Z' is the longest string, such that all +//! the input keys that start with 'K...' start with 'KZ...' as well. Note, the order of the input +//! keys in this implementation forces 'Z' to be a zeros string. +//! +//! Hash of a node depends on the number of edges coming out of it: +//! - A leaf: The hash is the input value of its key. +//! - A single edge: hash(child_hash, edge_mark) + edge_length. +//! - '0' and '1' edges: hash(zero_child_hash, one_child_hash). + +#[cfg(test)] +#[path = "patricia_hash_test.rs"] +mod patricia_hash_test; + +use bitvec::prelude::{BitArray, Msb0}; +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::StarkHash as CoreStarkHash; + +const TREE_HEIGHT: u8 = 64; +type BitPath = BitArray<[u8; 8], Msb0>; + +// An entry in a Patricia tree. +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct Entry { + key: BitPath, + value: Felt, +} + +// A sub-tree is defined by a sub-sequence of leaves with a common ancestor at the specified height, +// with no other leaves under it besides these. +#[derive(Debug)] +struct SubTree<'a> { + leaves: &'a [Entry], + // Levels from the root. + height: u8, +} + +enum SubTreeSplitting { + // Number of '0' bits that all the keys start with. + CommonZerosPrefix(u8), + // The index of the first key that starts with a '1' bit. + PartitionPoint(usize), +} + +/// Calculates Patricia hash root on the given values. +/// The values are keyed by consecutive numbers, starting from 0. +pub fn calculate_root(values: Vec) -> Felt { + if values.is_empty() { + return Felt::ZERO; + } + let leaves: Vec = values + .into_iter() + .zip(0u64..) + .map(|(felt, idx)| Entry { key: idx.to_be_bytes().into(), value: felt }) + .collect(); + get_hash::(SubTree { leaves: &leaves[..], height: 0_u8 }) +} + +// Recursive hash calculation. There are 3 cases: +// - Leaf: The sub tree height is maximal. It should contain exactly one entry. +// - Edge: All the keys start with a longest common ('0's) prefix. NOTE: We assume that the keys are +// a continuous range, and hence the case of '1's in the longest common prefix is impossible. +// - Binary: Some keys start with '0' bit and some start with '1' bit. +fn get_hash(sub_tree: SubTree<'_>) -> Felt { + if sub_tree.height == TREE_HEIGHT { + return sub_tree.leaves.first().expect("a leaf should not be empty").value; + } + match get_splitting(&sub_tree) { + SubTreeSplitting::CommonZerosPrefix(n_zeros) => get_edge_hash::(sub_tree, n_zeros), + SubTreeSplitting::PartitionPoint(partition_point) => { + get_binary_hash::(sub_tree, partition_point) + } + } +} + +// Hash on a '0's sequence with the bottom sub tree. +fn get_edge_hash(sub_tree: SubTree<'_>, n_zeros: u8) -> Felt { + let child_hash = + get_hash::(SubTree { leaves: sub_tree.leaves, height: sub_tree.height + n_zeros }); + let child_and_path_hash = H::hash(&child_hash, &Felt::ZERO); + child_and_path_hash + Felt::from(n_zeros) +} + +// Hash on both sides: starts with '0' bit and starts with '1' bit. +// Assumes: 0 < partition point < sub_tree.len(). +fn get_binary_hash(sub_tree: SubTree<'_>, partition_point: usize) -> Felt { + let zero_hash = get_hash::(SubTree { + leaves: &sub_tree.leaves[..partition_point], + height: sub_tree.height + 1, + }); + let one_hash = get_hash::(SubTree { + leaves: &sub_tree.leaves[partition_point..], + height: sub_tree.height + 1, + }); + H::hash(&zero_hash, &one_hash) +} + +// Returns the manner the keys of a subtree are splitting: some keys start with '1' or all keys +// start with '0'. +fn get_splitting(sub_tree: &SubTree<'_>) -> SubTreeSplitting { + let mut height = sub_tree.height; + + let first_one_bit_index = + sub_tree.leaves.partition_point(|entry| !entry.key[usize::from(height)]); + if first_one_bit_index < sub_tree.leaves.len() { + return SubTreeSplitting::PartitionPoint(first_one_bit_index); + } + + height += 1; + let mut n_zeros = 1; + + while height < TREE_HEIGHT { + if sub_tree.leaves.last().expect("sub tree should not be empty").key[usize::from(height)] { + break; + } + n_zeros += 1; + height += 1; + } + SubTreeSplitting::CommonZerosPrefix(n_zeros) +} diff --git a/crates/starknet_api/src/crypto/patricia_hash_test.rs b/crates/starknet_api/src/crypto/patricia_hash_test.rs new file mode 100644 index 00000000000..92c03bbad85 --- /dev/null +++ b/crates/starknet_api/src/crypto/patricia_hash_test.rs @@ -0,0 +1,28 @@ +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::Poseidon; + +use super::calculate_root; +use crate::felt; +use crate::hash::{FeltConverter, TryIntoFelt}; + +#[test] +fn test_patricia_regression() { + let root = + calculate_root::(vec![Felt::from(1_u8), Felt::from(2_u8), Felt::from(3_u8)]); + let expected_root = felt!("0x3b5cc7f1292eb3847c3f902d048a7e5dc7702d1c191ccd17c2d33f797e6fc32"); + assert_eq!(root, expected_root); +} + +#[test] +fn test_edge_patricia_regression() { + let root = calculate_root::(vec![Felt::from(1_u8)]); + let expected_root = felt!("0x7752582c54a42fe0fa35c40f07293bb7d8efe90e21d8d2c06a7db52d7d9b7e1"); + assert_eq!(root, expected_root); +} + +#[test] +fn test_binary_patricia_regression() { + let root = calculate_root::(vec![Felt::from(1_u8), Felt::from(2_u8)]); + let expected_root = felt!("0x1c1ba983ee0a0de87d87d67ea3cbee7023aa65f6b7bcf71259f122ea3af80bf"); + assert_eq!(root, expected_root); +} diff --git a/crates/starknet-api/src/crypto.rs b/crates/starknet_api/src/crypto/utils.rs similarity index 60% rename from crates/starknet-api/src/crypto.rs rename to crates/starknet_api/src/crypto/utils.rs index 83503dcce7a..170de7e4cee 100644 --- a/crates/starknet-api/src/crypto.rs +++ b/crates/starknet_api/src/crypto/utils.rs @@ -2,52 +2,68 @@ //! This module provides cryptographic utilities. #[cfg(test)] #[path = "crypto_test.rs"] +#[allow(clippy::explicit_auto_deref)] mod crypto_test; +use std::fmt; +use std::fmt::LowerHex; + use serde::{Deserialize, Serialize}; -use starknet_crypto::{pedersen_hash, poseidon_hash_many, FieldElement}; +use starknet_types_core::felt::Felt; +use starknet_types_core::hash::{Pedersen, Poseidon, StarkHash as CoreStarkHash}; -use crate::hash::{StarkFelt, StarkHash}; +use crate::hash::StarkHash; /// An error that can occur during cryptographic operations. + #[derive(thiserror::Error, Clone, Debug)] pub enum CryptoError { - #[error("Invalid public key {0:?}.")] + #[error("Invalid public key {0:#x}.")] InvalidPublicKey(PublicKey), - #[error("Invalid message hash {0:?}.")] - InvalidMessageHash(StarkFelt), - #[error("Invalid r {0:?}.")] - InvalidR(StarkFelt), - #[error("Invalid s {0:?}.")] - InvalidS(StarkFelt), + #[error("Invalid message hash {0:#x}.")] + InvalidMessageHash(Felt), + #[error("Invalid r {0}.")] + InvalidR(Felt), + #[error("Invalid s {0}.")] + InvalidS(Felt), } /// A public key. #[derive( Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, )] -pub struct PublicKey(pub StarkFelt); +pub struct PublicKey(pub Felt); + +impl LowerHex for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::LowerHex::fmt(&self.0, f) + } +} /// A signature. #[derive( Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord, )] pub struct Signature { - pub r: StarkFelt, - pub s: StarkFelt, + pub r: Felt, + pub s: Felt, +} + +fn to_field_element(felt: &Felt) -> starknet_crypto::FieldElement { + starknet_crypto::FieldElement::from_mont(felt.to_raw_reversed()) } /// Verifies the authenticity of a signed message hash given the public key of the signer. pub fn verify_message_hash_signature( - message_hash: &StarkFelt, + message_hash: &Felt, signature: &Signature, public_key: &PublicKey, ) -> Result { starknet_crypto::verify( - &public_key.0.into(), - &FieldElement::from(*message_hash), - &signature.r.into(), - &signature.s.into(), + &to_field_element(&public_key.0), + &to_field_element(message_hash), + &to_field_element(&signature.r), + &to_field_element(&signature.s), ) .map_err(|err| match err { starknet_crypto::VerifyError::InvalidPublicKey => { @@ -63,7 +79,7 @@ pub fn verify_message_hash_signature( // Collect elements for applying hash chain. pub(crate) struct HashChain { - elements: Vec, + elements: Vec, } impl HashChain { @@ -72,13 +88,13 @@ impl HashChain { } // Chains a felt to the hash chain. - pub fn chain(mut self, felt: &StarkFelt) -> Self { - self.elements.push(FieldElement::from(*felt)); + pub fn chain(mut self, felt: &Felt) -> Self { + self.elements.push(*felt); self } // Chains the result of a function to the hash chain. - pub fn chain_if_fn Option>(self, f: F) -> Self { + pub fn chain_if_fn Option>(self, f: F) -> Self { match f() { Some(felt) => self.chain(&felt), None => self, @@ -86,27 +102,28 @@ impl HashChain { } // Chains many felts to the hash chain. - pub fn chain_iter<'a>(self, felts: impl Iterator) -> Self { + pub fn chain_iter<'a>(self, felts: impl Iterator) -> Self { felts.fold(self, |current, felt| current.chain(felt)) } // Chains the number of felts followed by the felts themselves to the hash chain. - pub fn chain_size_and_elements(self, felts: &[StarkFelt]) -> Self { + pub fn chain_size_and_elements(self, felts: &[Felt]) -> Self { self.chain(&felts.len().into()).chain_iter(felts.iter()) } + // Chains a chain of felts to the hash chain. + pub fn extend(mut self, chain: HashChain) -> Self { + self.elements.extend(chain.elements); + self + } + // Returns the pedersen hash of the chained felts, hashed with the length of the chain. pub fn get_pedersen_hash(&self) -> StarkHash { - let current_hash = self - .elements - .iter() - .fold(FieldElement::ZERO, |current_hash, felt| pedersen_hash(¤t_hash, felt)); - let n_elements = FieldElement::from(self.elements.len()); - pedersen_hash(¤t_hash, &n_elements).into() + Pedersen::hash_array(self.elements.as_slice()) } // Returns the poseidon hash of the chained felts. pub fn get_poseidon_hash(&self) -> StarkHash { - poseidon_hash_many(&self.elements).into() + Poseidon::hash_array(self.elements.as_slice()) } } diff --git a/crates/starknet-api/src/data_availability.rs b/crates/starknet_api/src/data_availability.rs similarity index 71% rename from crates/starknet-api/src/data_availability.rs rename to crates/starknet_api/src/data_availability.rs index 0b5ec76d0ee..0fc3f948009 100644 --- a/crates/starknet-api/src/data_availability.rs +++ b/crates/starknet_api/src/data_availability.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; +use starknet_types_core::felt::Felt; -use crate::hash::StarkFelt; use crate::StarknetApiError; #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] @@ -39,25 +39,27 @@ impl TryFrom for DataAvailabilityMode { } } -impl TryFrom for DataAvailabilityMode { +impl TryFrom for DataAvailabilityMode { type Error = StarknetApiError; - fn try_from(felt: StarkFelt) -> Result { - match felt { - StarkFelt::ZERO => Ok(DataAvailabilityMode::L1), - StarkFelt::ONE => Ok(DataAvailabilityMode::L2), - _ => Err(StarknetApiError::OutOfRange { - string: format!("Invalid data availability mode: {felt}."), - }), + fn try_from(felt: Felt) -> Result { + if felt == Felt::ZERO { + return Ok(DataAvailabilityMode::L1); + } + if felt == Felt::ONE { + return Ok(DataAvailabilityMode::L2); } + Err(StarknetApiError::OutOfRange { + string: format!("Invalid data availability mode: {felt}."), + }) } } -impl From for StarkFelt { - fn from(data_availability_mode: DataAvailabilityMode) -> StarkFelt { +impl From for Felt { + fn from(data_availability_mode: DataAvailabilityMode) -> Felt { match data_availability_mode { - DataAvailabilityMode::L1 => StarkFelt::ZERO, - DataAvailabilityMode::L2 => StarkFelt::ONE, + DataAvailabilityMode::L1 => Felt::ZERO, + DataAvailabilityMode::L2 => Felt::ONE, } } } diff --git a/crates/starknet-api/src/deprecated_contract_class.rs b/crates/starknet_api/src/deprecated_contract_class.rs similarity index 98% rename from crates/starknet-api/src/deprecated_contract_class.rs rename to crates/starknet_api/src/deprecated_contract_class.rs index 99f5a0b569b..eb5de5db907 100644 --- a/crates/starknet-api/src/deprecated_contract_class.rs +++ b/crates/starknet_api/src/deprecated_contract_class.rs @@ -8,6 +8,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use crate::core::EntryPointSelector; +use crate::hash::StarkHash; use crate::serde_utils::deserialize_optional_contract_class_abi_entry_vector; use crate::StarknetApiError; @@ -193,7 +194,7 @@ impl TryFrom for EntryPoint { fn try_from(value: CasmContractEntryPoint) -> Result { Ok(EntryPoint { - selector: EntryPointSelector(value.selector.to_str_radix(16).as_str().try_into()?), + selector: EntryPointSelector(StarkHash::from(value.selector)), offset: EntryPointOffset(value.offset), }) } diff --git a/crates/starknet-api/src/external_transaction.rs b/crates/starknet_api/src/external_transaction.rs similarity index 98% rename from crates/starknet-api/src/external_transaction.rs rename to crates/starknet_api/src/external_transaction.rs index d64c5f3a8d3..935d7da2a87 100644 --- a/crates/starknet-api/src/external_transaction.rs +++ b/crates/starknet_api/src/external_transaction.rs @@ -54,8 +54,10 @@ macro_rules! implement_ref_getters { impl ExternalTransaction { implement_ref_getters!( + (nonce, Nonce), (resource_bounds, ResourceBoundsMapping), - (signature, TransactionSignature) + (signature, TransactionSignature), + (tip, Tip) ); } diff --git a/crates/starknet-api/src/external_transaction_test.rs b/crates/starknet_api/src/external_transaction_test.rs similarity index 70% rename from crates/starknet-api/src/external_transaction_test.rs rename to crates/starknet_api/src/external_transaction_test.rs index 0e8974431d2..e9eb09cef24 100644 --- a/crates/starknet-api/src/external_transaction_test.rs +++ b/crates/starknet_api/src/external_transaction_test.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::sync::Arc; use rstest::rstest; +use starknet_types_core::felt::Felt; use crate::core::{ClassHash, CompiledClassHash, ContractAddress, Nonce, PatriciaKey}; use crate::external_transaction::{ @@ -15,7 +16,7 @@ use crate::external_transaction::{ ExternalInvokeTransactionV3, ExternalTransaction, }; -use crate::hash::{StarkFelt, StarkHash}; +use crate::hash::{FeltConverter, TryIntoFelt}; use crate::transaction::{ AccountDeploymentData, Calldata, @@ -27,7 +28,7 @@ use crate::transaction::{ Tip, TransactionSignature, }; -use crate::{contract_address, patricia_key, stark_felt}; +use crate::{contract_address, felt, patricia_key}; fn create_resource_bounds() -> ResourceBoundsMapping { let mut map = BTreeMap::new(); @@ -41,14 +42,14 @@ fn create_declare_v3() -> ExternalDeclareTransaction { contract_class: ContractClass::default(), resource_bounds: create_resource_bounds(), tip: Tip(1), - signature: TransactionSignature(vec![StarkFelt::ONE, StarkFelt::TWO]), - nonce: Nonce(stark_felt!("0x1")), - compiled_class_hash: CompiledClassHash(stark_felt!("0x2")), + signature: TransactionSignature(vec![Felt::ONE, Felt::TWO]), + nonce: Nonce(Felt::ONE), + compiled_class_hash: CompiledClassHash(Felt::TWO), sender_address: contract_address!("0x3"), nonce_data_availability_mode: DataAvailabilityMode::L1, fee_data_availability_mode: DataAvailabilityMode::L2, - paymaster_data: PaymasterData(vec![StarkFelt::ZERO]), - account_deployment_data: AccountDeploymentData(vec![StarkFelt::THREE]), + paymaster_data: PaymasterData(vec![Felt::ZERO]), + account_deployment_data: AccountDeploymentData(vec![Felt::THREE]), }) } @@ -56,14 +57,14 @@ fn create_deploy_account_v3() -> ExternalDeployAccountTransaction { ExternalDeployAccountTransaction::V3(ExternalDeployAccountTransactionV3 { resource_bounds: create_resource_bounds(), tip: Tip::default(), - contract_address_salt: ContractAddressSalt(stark_felt!("0x23")), - class_hash: ClassHash(stark_felt!("0x2")), - constructor_calldata: Calldata(Arc::new(vec![StarkFelt::ZERO])), - nonce: Nonce(stark_felt!("0x60")), - signature: TransactionSignature(vec![StarkFelt::TWO]), + contract_address_salt: ContractAddressSalt(felt!("0x23")), + class_hash: ClassHash(Felt::TWO), + constructor_calldata: Calldata(Arc::new(vec![Felt::ZERO])), + nonce: Nonce(felt!("0x60")), + signature: TransactionSignature(vec![Felt::TWO]), nonce_data_availability_mode: DataAvailabilityMode::L2, fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: PaymasterData(vec![StarkFelt::TWO, StarkFelt::ZERO]), + paymaster_data: PaymasterData(vec![Felt::TWO, Felt::ZERO]), }) } @@ -71,14 +72,14 @@ fn create_invoke_v3() -> ExternalInvokeTransaction { ExternalInvokeTransaction::V3(ExternalInvokeTransactionV3 { resource_bounds: create_resource_bounds(), tip: Tip(50), - calldata: Calldata(Arc::new(vec![stark_felt!("0x2000"), stark_felt!("0x1000")])), + calldata: Calldata(Arc::new(vec![felt!("0x2000"), felt!("0x1000")])), sender_address: contract_address!("0x53"), - nonce: Nonce(stark_felt!("0x32")), + nonce: Nonce(felt!("0x32")), signature: TransactionSignature::default(), nonce_data_availability_mode: DataAvailabilityMode::L1, fee_data_availability_mode: DataAvailabilityMode::L1, - paymaster_data: PaymasterData(vec![StarkFelt::TWO, StarkFelt::ZERO]), - account_deployment_data: AccountDeploymentData(vec![stark_felt!("0x87")]), + paymaster_data: PaymasterData(vec![Felt::TWO, Felt::ZERO]), + account_deployment_data: AccountDeploymentData(vec![felt!("0x87")]), }) } diff --git a/crates/starknet_api/src/hash.rs b/crates/starknet_api/src/hash.rs new file mode 100644 index 00000000000..c3be938e1c1 --- /dev/null +++ b/crates/starknet_api/src/hash.rs @@ -0,0 +1,51 @@ +use std::fmt::Debug; + +use serde::{Deserialize, Serialize}; +use sha3::{Digest, Keccak256}; +use starknet_types_core::felt::Felt; + +pub type StarkHash = Felt; + +#[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] +pub struct PoseidonHash(pub Felt); + +/// Computes the first 250 bits of the Keccak256 hash, in order to fit into a field element. +pub fn starknet_keccak_hash(input: &[u8]) -> Felt { + let mut keccak = Keccak256::default(); + keccak.update(input); + let mut hashed_bytes: [u8; 32] = keccak.finalize().into(); + hashed_bytes[0] &= 0b00000011_u8; // Discard the six MSBs. + Felt::from_bytes_be(&hashed_bytes) +} + +#[cfg(any(feature = "testing", test))] +pub struct FeltConverter; + +#[cfg(any(feature = "testing", test))] +pub trait TryIntoFelt { + fn to_felt_unchecked(v: V) -> Felt; +} + +#[cfg(any(feature = "testing", test))] +impl TryIntoFelt for FeltConverter { + fn to_felt_unchecked(v: u128) -> Felt { + Felt::from(v) + } +} + +#[cfg(any(feature = "testing", test))] +impl TryIntoFelt<&str> for FeltConverter { + fn to_felt_unchecked(v: &str) -> Felt { + Felt::from_hex_unchecked(v) + } +} + +/// A utility macro to create a [`starknet_types_core::felt::Felt`] from an intergert or a hex +/// string representation. +#[cfg(any(feature = "testing", test))] +#[macro_export] +macro_rules! felt { + ($s:expr) => { + FeltConverter::to_felt_unchecked($s) + }; +} diff --git a/crates/starknet-api/src/internal_transaction.rs b/crates/starknet_api/src/internal_transaction.rs similarity index 100% rename from crates/starknet-api/src/internal_transaction.rs rename to crates/starknet_api/src/internal_transaction.rs diff --git a/crates/starknet-api/src/lib.rs b/crates/starknet_api/src/lib.rs similarity index 100% rename from crates/starknet-api/src/lib.rs rename to crates/starknet_api/src/lib.rs diff --git a/crates/starknet-api/src/serde_utils.rs b/crates/starknet_api/src/serde_utils.rs similarity index 100% rename from crates/starknet-api/src/serde_utils.rs rename to crates/starknet_api/src/serde_utils.rs diff --git a/crates/starknet-api/src/serde_utils_test.rs b/crates/starknet_api/src/serde_utils_test.rs similarity index 99% rename from crates/starknet-api/src/serde_utils_test.rs rename to crates/starknet_api/src/serde_utils_test.rs index de8cac3e779..2a94ae6379d 100644 --- a/crates/starknet-api/src/serde_utils_test.rs +++ b/crates/starknet_api/src/serde_utils_test.rs @@ -161,7 +161,7 @@ fn deserialize_valid_optional_contract_class_abi_entry_vector() { outputs: vec![], state_mutability: None, r#type: ConstructorType::Constructor, - },)]) + })]) } ); } diff --git a/crates/starknet-api/src/state.rs b/crates/starknet_api/src/state.rs similarity index 97% rename from crates/starknet-api/src/state.rs rename to crates/starknet_api/src/state.rs index 003c636c0f7..ed6dd2ef707 100644 --- a/crates/starknet-api/src/state.rs +++ b/crates/starknet_api/src/state.rs @@ -7,6 +7,7 @@ use std::fmt::Debug; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; +use starknet_types_core::felt::Felt; use crate::block::{BlockHash, BlockNumber}; use crate::core::{ @@ -19,7 +20,7 @@ use crate::core::{ PatriciaKey, }; use crate::deprecated_contract_class::ContractClass as DeprecatedContractClass; -use crate::hash::{StarkFelt, StarkHash}; +use crate::hash::StarkHash; use crate::{impl_from_through_intermediate, StarknetApiError}; pub type DeclaredClasses = IndexMap; @@ -42,7 +43,7 @@ pub struct StateUpdate { #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct StateDiff { pub deployed_contracts: IndexMap, - pub storage_diffs: IndexMap>, + pub storage_diffs: IndexMap>, pub declared_classes: IndexMap, pub deprecated_declared_classes: IndexMap, pub nonces: IndexMap, @@ -55,7 +56,7 @@ pub struct StateDiff { #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct ThinStateDiff { pub deployed_contracts: IndexMap, - pub storage_diffs: IndexMap>, + pub storage_diffs: IndexMap>, pub declared_classes: IndexMap, pub deprecated_declared_classes: Vec, pub nonces: IndexMap, @@ -179,8 +180,8 @@ impl StateNumber { )] pub struct StorageKey(pub PatriciaKey); -impl From for StarkFelt { - fn from(storage_key: StorageKey) -> StarkFelt { +impl From for Felt { + fn from(storage_key: StorageKey) -> Felt { **storage_key } } @@ -204,7 +205,7 @@ impl_from_through_intermediate!(u128, StorageKey, u8, u16, u32, u64); /// A contract class. #[derive(Debug, Clone, Default, Eq, PartialEq, Deserialize, Serialize)] pub struct ContractClass { - pub sierra_program: Vec, + pub sierra_program: Vec, pub entry_points_by_type: HashMap>, pub abi: String, } diff --git a/crates/starknet-api/src/state_test.rs b/crates/starknet_api/src/state_test.rs similarity index 100% rename from crates/starknet-api/src/state_test.rs rename to crates/starknet_api/src/state_test.rs diff --git a/crates/starknet-api/src/transaction.rs b/crates/starknet_api/src/transaction.rs similarity index 90% rename from crates/starknet-api/src/transaction.rs rename to crates/starknet_api/src/transaction.rs index 1985155983a..db4ba6a1bb5 100644 --- a/crates/starknet-api/src/transaction.rs +++ b/crates/starknet_api/src/transaction.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use derive_more::From; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use starknet_types_core::felt::Felt; use strum::IntoEnumIterator; use strum_macros::EnumIter; @@ -18,7 +19,7 @@ use crate::core::{ Nonce, }; use crate::data_availability::DataAvailabilityMode; -use crate::hash::{StarkFelt, StarkHash}; +use crate::hash::StarkHash; use crate::serde_utils::PrefixedBytesAsHex; use crate::transaction_hash::{ get_declare_transaction_v0_hash, @@ -128,6 +129,16 @@ impl TransactionOutput { } } + pub fn execution_status(&self) -> &TransactionExecutionStatus { + match self { + TransactionOutput::Declare(output) => &output.execution_status, + TransactionOutput::Deploy(output) => &output.execution_status, + TransactionOutput::DeployAccount(output) => &output.execution_status, + TransactionOutput::Invoke(output) => &output.execution_status, + TransactionOutput::L1Handler(output) => &output.execution_status, + } + } + pub fn execution_resources(&self) -> &ExecutionResources { match self { TransactionOutput::Declare(output) => &output.execution_resources, @@ -137,6 +148,38 @@ impl TransactionOutput { TransactionOutput::L1Handler(output) => &output.execution_resources, } } + + pub fn messages_sent(&self) -> &Vec { + match self { + TransactionOutput::Declare(output) => &output.messages_sent, + TransactionOutput::Deploy(output) => &output.messages_sent, + TransactionOutput::DeployAccount(output) => &output.messages_sent, + TransactionOutput::Invoke(output) => &output.messages_sent, + TransactionOutput::L1Handler(output) => &output.messages_sent, + } + } +} + +/// The common fields of transaction output types. +#[derive(Clone)] +pub struct TransactionOutputCommon { + pub actual_fee: Fee, + pub events: Vec, + pub execution_status: TransactionExecutionStatus, + pub execution_resources: ExecutionResources, + pub messages_sent: Vec, +} + +impl From for TransactionOutputCommon { + fn from(transaction_output: TransactionOutput) -> Self { + Self { + actual_fee: transaction_output.actual_fee(), + events: transaction_output.events().to_vec(), + execution_status: transaction_output.execution_status().to_owned(), + execution_resources: transaction_output.execution_resources().to_owned(), + messages_sent: transaction_output.messages_sent().to_owned(), + } + } } /// A declare V0 or V1 transaction (same schema but different version). @@ -155,15 +198,13 @@ impl TransactionHasher for DeclareTransactionV0V1 { chain_id: &ChainId, transaction_version: &TransactionVersion, ) -> Result { - match *transaction_version { - TransactionVersion::ZERO => { - get_declare_transaction_v0_hash(self, chain_id, transaction_version) - } - TransactionVersion::ONE => { - get_declare_transaction_v1_hash(self, chain_id, transaction_version) - } - _ => panic!("Illegal transaction version."), + if *transaction_version == TransactionVersion::ZERO { + return get_declare_transaction_v0_hash(self, chain_id, transaction_version); + } + if *transaction_version == TransactionVersion::ONE { + return get_declare_transaction_v1_hash(self, chain_id, transaction_version); } + panic!("Illegal transaction version."); } } @@ -629,6 +670,7 @@ pub enum TransactionExecutionStatus { /// A reverted transaction execution status. #[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub struct RevertedTransactionExecutionStatus { + // TODO: Validate it's an ASCII string. pub revert_reason: String, } @@ -662,7 +704,7 @@ impl From for PrefixedBytesAsHex<16_usize> { } } -impl From for StarkFelt { +impl From for Felt { fn from(fee: Fee) -> Self { Self::from(fee.0) } @@ -699,7 +741,7 @@ pub struct ContractAddressSalt(pub StarkHash); /// A transaction signature. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct TransactionSignature(pub Vec); +pub struct TransactionSignature(pub Vec); /// A transaction version. #[derive( @@ -716,25 +758,25 @@ pub struct TransactionSignature(pub Vec); Ord, derive_more::Deref, )] -pub struct TransactionVersion(pub StarkFelt); +pub struct TransactionVersion(pub Felt); impl TransactionVersion { /// [TransactionVersion] constant that's equal to 0. - pub const ZERO: Self = { Self(StarkFelt::ZERO) }; + pub const ZERO: Self = { Self(Felt::ZERO) }; /// [TransactionVersion] constant that's equal to 1. - pub const ONE: Self = { Self(StarkFelt::ONE) }; + pub const ONE: Self = { Self(Felt::ONE) }; /// [TransactionVersion] constant that's equal to 2. - pub const TWO: Self = { Self(StarkFelt::TWO) }; + pub const TWO: Self = { Self(Felt::TWO) }; /// [TransactionVersion] constant that's equal to 3. - pub const THREE: Self = { Self(StarkFelt::THREE) }; + pub const THREE: Self = { Self(Felt::THREE) }; } /// The calldata of a transaction. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct Calldata(pub Arc>); +pub struct Calldata(pub Arc>); #[macro_export] macro_rules! calldata { @@ -760,15 +802,16 @@ pub struct MessageToL1 { /// The payload of [`MessageToL2`]. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct L1ToL2Payload(pub Vec); +pub struct L1ToL2Payload(pub Vec); /// The payload of [`MessageToL1`]. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct L2ToL1Payload(pub Vec); +pub struct L2ToL1Payload(pub Vec); /// An event. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] pub struct Event { + // TODO: Add a TransactionHash element to this struct, and then remove EventLeafElements. pub from_address: ContractAddress, #[serde(flatten)] pub content: EventContent, @@ -783,11 +826,11 @@ pub struct EventContent { /// An event key. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct EventKey(pub StarkFelt); +pub struct EventKey(pub Felt); /// An event data. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct EventData(pub Vec); +pub struct EventData(pub Vec); /// The index of a transaction in [BlockBody](`crate::block::BlockBody`). #[derive( @@ -831,7 +874,7 @@ impl From for PrefixedBytesAsHex<8_usize> { } } -impl From for StarkFelt { +impl From for Felt { fn from(tip: Tip) -> Self { Self::from(tip.0) } @@ -920,12 +963,18 @@ impl TryFrom> for ResourceBoundsMapping { /// Paymaster-related data. #[derive(Clone, Debug, Default, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize)] -pub struct PaymasterData(pub Vec); +pub struct PaymasterData(pub Vec); /// If nonempty, will contain the required data for deploying and initializing an account contract: /// its class hash, address salt and constructor calldata. #[derive(Debug, Clone, Default, Eq, PartialEq, Hash, Deserialize, Serialize, PartialOrd, Ord)] -pub struct AccountDeploymentData(pub Vec); +pub struct AccountDeploymentData(pub Vec); + +#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] +pub struct GasVector { + pub l1_gas: u64, + pub l1_data_gas: u64, +} /// The execution resources used by a transaction. #[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)] @@ -933,8 +982,8 @@ pub struct ExecutionResources { pub steps: u64, pub builtin_instance_counter: HashMap, pub memory_holes: u64, - pub da_l1_gas_consumed: u64, - pub da_l1_data_gas_consumed: u64, + pub da_gas_consumed: GasVector, + pub gas_consumed: GasVector, } #[derive(Hash, Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] diff --git a/crates/starknet-api/src/transaction_hash.rs b/crates/starknet_api/src/transaction_hash.rs similarity index 86% rename from crates/starknet-api/src/transaction_hash.rs rename to crates/starknet_api/src/transaction_hash.rs index f6ba6ddc7fe..b7daf1fd976 100644 --- a/crates/starknet-api/src/transaction_hash.rs +++ b/crates/starknet_api/src/transaction_hash.rs @@ -1,10 +1,10 @@ use once_cell::sync::Lazy; +use starknet_types_core::felt::Felt; use crate::block::BlockNumber; use crate::core::{calculate_contract_address, ChainId, ContractAddress}; -use crate::crypto::HashChain; +use crate::crypto::utils::HashChain; use crate::data_availability::DataAvailabilityMode; -use crate::hash::StarkFelt; use crate::transaction::{ DeclareTransaction, DeclareTransactionV0V1, @@ -35,21 +35,19 @@ const DATA_AVAILABILITY_MODE_BITS: usize = 32; const L1_GAS: &ResourceName = b"\0L1_GAS"; const L2_GAS: &ResourceName = b"\0L2_GAS"; -static DECLARE: Lazy = +static DECLARE: Lazy = Lazy::new(|| ascii_as_felt("declare").expect("ascii_as_felt failed for 'declare'")); -static DEPLOY: Lazy = +static DEPLOY: Lazy = Lazy::new(|| ascii_as_felt("deploy").expect("ascii_as_felt failed for 'deploy'")); -static DEPLOY_ACCOUNT: Lazy = Lazy::new(|| { +static DEPLOY_ACCOUNT: Lazy = Lazy::new(|| { ascii_as_felt("deploy_account").expect("ascii_as_felt failed for 'deploy_account'") }); -static INVOKE: Lazy = +static INVOKE: Lazy = Lazy::new(|| ascii_as_felt("invoke").expect("ascii_as_felt failed for 'invoke'")); -static L1_HANDLER: Lazy = +static L1_HANDLER: Lazy = Lazy::new(|| ascii_as_felt("l1_handler").expect("ascii_as_felt failed for 'l1_handler'")); -static CONSTRUCTOR_ENTRY_POINT_SELECTOR: Lazy = Lazy::new(|| { - StarkFelt::try_from("0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194") - .expect("Failed to create StarkFelt from value") -}); +const CONSTRUCTOR_ENTRY_POINT_SELECTOR: Felt = + Felt::from_hex_unchecked("0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194"); /// Calculates hash of a Starknet transaction. pub fn get_transaction_hash( @@ -119,40 +117,32 @@ fn get_deprecated_transaction_hashes( transaction: &Transaction, transaction_version: &TransactionVersion, ) -> Result, StarknetApiError> { - Ok( - if chain_id == &ChainId("SN_MAIN".to_string()) - && block_number > &MAINNET_TRANSACTION_HASH_WITH_VERSION - { - vec![] - } else { - match transaction { - Transaction::Declare(_) => vec![], - Transaction::Deploy(deploy) => { - vec![get_deprecated_deploy_transaction_hash( - deploy, + Ok(if chain_id == &ChainId::Mainnet && block_number > &MAINNET_TRANSACTION_HASH_WITH_VERSION { + vec![] + } else { + match transaction { + Transaction::Declare(_) => vec![], + Transaction::Deploy(deploy) => { + vec![get_deprecated_deploy_transaction_hash(deploy, chain_id, transaction_version)?] + } + Transaction::DeployAccount(_) => vec![], + Transaction::Invoke(invoke) => match invoke { + InvokeTransaction::V0(invoke_v0) => { + vec![get_deprecated_invoke_transaction_v0_hash( + invoke_v0, chain_id, transaction_version, )?] } - Transaction::DeployAccount(_) => vec![], - Transaction::Invoke(invoke) => match invoke { - InvokeTransaction::V0(invoke_v0) => { - vec![get_deprecated_invoke_transaction_v0_hash( - invoke_v0, - chain_id, - transaction_version, - )?] - } - InvokeTransaction::V1(_) | InvokeTransaction::V3(_) => vec![], - }, - Transaction::L1Handler(l1_handler) => get_deprecated_l1_handler_transaction_hashes( - l1_handler, - chain_id, - transaction_version, - )?, - } - }, - ) + InvokeTransaction::V1(_) | InvokeTransaction::V3(_) => vec![], + }, + Transaction::L1Handler(l1_handler) => get_deprecated_l1_handler_transaction_hashes( + l1_handler, + chain_id, + transaction_version, + )?, + } + }) } /// Validates the hash of a starknet transaction. @@ -177,15 +167,17 @@ pub fn validate_transaction_hash( Ok(possible_hashes.contains(&expected_hash)) } -pub(crate) fn ascii_as_felt(ascii_str: &str) -> Result { - StarkFelt::try_from(hex::encode(ascii_str).as_str()) +// TODO: should be part of core::Felt +pub(crate) fn ascii_as_felt(ascii_str: &str) -> Result { + Felt::from_hex(hex::encode(ascii_str).as_str()) + .map_err(|_| StarknetApiError::OutOfRange { string: ascii_str.to_string() }) } // An implementation of the SNIP: https://github.com/EvyatarO/SNIPs/blob/snip-8/SNIPS/snip-8.md fn get_tip_resource_bounds_hash( resource_bounds_mapping: &ResourceBoundsMapping, tip: &Tip, -) -> Result { +) -> Result { let l1_resource_bounds = resource_bounds_mapping.0.get(&Resource::L1Gas).expect("Missing l1 resource"); let l1_resource = get_concat_resource(l1_resource_bounds, L1_GAS)?; @@ -207,13 +199,13 @@ fn get_tip_resource_bounds_hash( fn get_concat_resource( resource_bounds: &ResourceBounds, resource_name: &ResourceName, -) -> Result { +) -> Result { let max_amount = resource_bounds.max_amount.to_be_bytes(); let max_price = resource_bounds.max_price_per_unit.to_be_bytes(); let concat_bytes = [[0_u8].as_slice(), resource_name.as_slice(), max_amount.as_slice(), max_price.as_slice()] .concat(); - StarkFelt::new(concat_bytes.try_into().expect("Expect 32 bytes")) + Ok(Felt::from_bytes_be(&concat_bytes.try_into().expect("Expect 32 bytes"))) } // Receives nonce_mode and fee_mode and returns: @@ -222,7 +214,7 @@ fn get_concat_resource( fn concat_data_availability_mode( nonce_mode: &DataAvailabilityMode, fee_mode: &DataAvailabilityMode, -) -> StarkFelt { +) -> Felt { (data_availability_mode_index(fee_mode) + (data_availability_mode_index(nonce_mode) << DATA_AVAILABILITY_MODE_BITS)) .into() @@ -284,12 +276,12 @@ fn get_common_deploy_transaction_hash( // No fee in deploy transaction. .chain_if_fn(|| { if !is_deprecated { - Some(StarkFelt::ZERO) + Some(Felt::ZERO) } else { None } }) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .get_pedersen_hash(), )) } @@ -324,7 +316,7 @@ fn get_common_invoke_transaction_v0_hash( .chain(&transaction.entry_point_selector.0) .chain(&HashChain::new().chain_iter(transaction.calldata.0.iter()).get_pedersen_hash()) .chain_if_fn(|| if !is_deprecated { Some(transaction.max_fee.0.into()) } else { None }) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .get_pedersen_hash(), )) } @@ -339,10 +331,10 @@ pub(crate) fn get_invoke_transaction_v1_hash( .chain(&INVOKE) .chain(&transaction_version.0) .chain(transaction.sender_address.0.key()) - .chain(&StarkFelt::ZERO) // No entry point selector in invoke transaction. + .chain(&Felt::ZERO) // No entry point selector in invoke transaction. .chain(&HashChain::new().chain_iter(transaction.calldata.0.iter()).get_pedersen_hash()) .chain(&transaction.max_fee.0.into()) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .get_pedersen_hash(), )) @@ -374,7 +366,7 @@ pub(crate) fn get_invoke_transaction_v3_hash( .chain(transaction.sender_address.0.key()) .chain(&tip_resource_bounds_hash) .chain(&paymaster_data_hash) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .chain(&data_availability_mode) .chain(&account_deployment_data_hash) @@ -452,12 +444,12 @@ fn get_common_l1_handler_transaction_hash( // No fee in l1 handler transaction. .chain_if_fn(|| { if version > L1HandlerVersions::V0Deprecated { - Some(StarkFelt::ZERO) + Some(Felt::ZERO) } else { None } }) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain_if_fn(|| { if version > L1HandlerVersions::AsInvoke { Some(transaction.nonce.0) @@ -479,10 +471,10 @@ pub(crate) fn get_declare_transaction_v0_hash( .chain(&DECLARE) .chain(&transaction_version.0) .chain(transaction.sender_address.0.key()) - .chain(&StarkFelt::ZERO) // No entry point selector in declare transaction. + .chain(&Felt::ZERO) // No entry point selector in declare transaction. .chain(&HashChain::new().get_pedersen_hash()) .chain(&transaction.max_fee.0.into()) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.class_hash.0) .get_pedersen_hash(), )) @@ -498,10 +490,10 @@ pub(crate) fn get_declare_transaction_v1_hash( .chain(&DECLARE) .chain(&transaction_version.0) .chain(transaction.sender_address.0.key()) - .chain(&StarkFelt::ZERO) // No entry point selector in declare transaction. + .chain(&Felt::ZERO) // No entry point selector in declare transaction. .chain(&HashChain::new().chain(&transaction.class_hash.0).get_pedersen_hash()) .chain(&transaction.max_fee.0.into()) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .get_pedersen_hash(), )) @@ -517,10 +509,10 @@ pub(crate) fn get_declare_transaction_v2_hash( .chain(&DECLARE) .chain(&transaction_version.0) .chain(transaction.sender_address.0.key()) - .chain(&StarkFelt::ZERO) // No entry point selector in declare transaction. + .chain(&Felt::ZERO) // No entry point selector in declare transaction. .chain(&HashChain::new().chain(&transaction.class_hash.0).get_pedersen_hash()) .chain(&transaction.max_fee.0.into()) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .chain(&transaction.compiled_class_hash.0) .get_pedersen_hash(), @@ -551,7 +543,7 @@ pub(crate) fn get_declare_transaction_v3_hash( .chain(transaction.sender_address.0.key()) .chain(&tip_resource_bounds_hash) .chain(&paymaster_data_hash) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .chain(&data_availability_mode) .chain(&account_deployment_data_hash) @@ -584,10 +576,10 @@ pub(crate) fn get_deploy_account_transaction_v1_hash( .chain(&DEPLOY_ACCOUNT) .chain(&transaction_version.0) .chain(contract_address.0.key()) - .chain(&StarkFelt::ZERO) // No entry point selector in deploy account transaction. + .chain(&Felt::ZERO) // No entry point selector in deploy account transaction. .chain(&calldata_hash) .chain(&transaction.max_fee.0.into()) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&transaction.nonce.0) .get_pedersen_hash(), )) @@ -622,7 +614,7 @@ pub(crate) fn get_deploy_account_transaction_v3_hash( .chain(contract_address.0.key()) .chain(&tip_resource_bounds_hash) .chain(&paymaster_data_hash) - .chain(&ascii_as_felt(chain_id.0.as_str())?) + .chain(&ascii_as_felt(chain_id.to_string().as_str())?) .chain(&data_availability_mode) .chain(&transaction.nonce.0) .chain(&constructor_calldata_hash) diff --git a/crates/starknet-api/src/type_utils.rs b/crates/starknet_api/src/type_utils.rs similarity index 100% rename from crates/starknet-api/src/type_utils.rs rename to crates/starknet_api/src/type_utils.rs diff --git a/crates/starknet_client/Cargo.toml b/crates/starknet_client/Cargo.toml index df40cc8da76..c336a848cb9 100644 --- a/crates/starknet_client/Cargo.toml +++ b/crates/starknet_client/Cargo.toml @@ -7,7 +7,7 @@ license-file.workspace = true description = "A client implementation that can communicate with Starknet." [features] -testing = ["enum-iterator", "mockall", "rand", "rand_chacha", "test_utils"] +testing = ["enum-iterator", "mockall", "rand", "rand_chacha", "papyrus_test_utils"] [dependencies] async-trait.workspace = true @@ -25,10 +25,10 @@ reqwest = { workspace = true, features = ["json", "blocking"] } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true, features = ["arbitrary_precision"] } serde_repr.workspace = true -starknet_api.workspace = true +starknet_api = { version = "0.12.0-dev.1" } strum.workspace = true strum_macros.workspace = true -test_utils = { path = "../test_utils", optional = true } +papyrus_test_utils = { path = "../papyrus_test_utils", optional = true } thiserror.workspace = true tokio = { workspace = true, features = ["full", "sync"] } tokio-retry.workspace = true @@ -44,5 +44,5 @@ rand.workspace = true rand_chacha.workspace = true pretty_assertions.workspace = true simple_logger.workspace = true -starknet_api = { workspace = true, features = ["testing"] } -test_utils = { path = "../test_utils" } +starknet_api = { version = "0.12.0-dev.1", features = ["testing"] } +papyrus_test_utils = { path = "../papyrus_test_utils" } diff --git a/crates/starknet_client/src/reader/objects/test_utils.rs b/crates/starknet_client/src/reader/objects/test_utils.rs index 5ec92dec480..1c4244f7b6a 100644 --- a/crates/starknet_client/src/reader/objects/test_utils.rs +++ b/crates/starknet_client/src/reader/objects/test_utils.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use papyrus_test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use starknet_api::core::{ ClassHash, CompiledClassHash, @@ -26,7 +27,6 @@ use starknet_api::transaction::{ TransactionSignature, TransactionVersion, }; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use crate::reader::objects::state::ContractClass; use crate::reader::objects::transaction::{ diff --git a/crates/starknet_client/src/writer/objects/response_test.rs b/crates/starknet_client/src/writer/objects/response_test.rs index 15d2231f2c8..59143569783 100644 --- a/crates/starknet_client/src/writer/objects/response_test.rs +++ b/crates/starknet_client/src/writer/objects/response_test.rs @@ -1,4 +1,4 @@ -use test_utils::validate_load_and_dump; +use papyrus_test_utils::validate_load_and_dump; use super::{DeclareResponse, DeployAccountResponse, InvokeResponse}; diff --git a/crates/starknet_client/src/writer/objects/test_utils.rs b/crates/starknet_client/src/writer/objects/test_utils.rs index ddc3dd85896..e13e17ccea3 100644 --- a/crates/starknet_client/src/writer/objects/test_utils.rs +++ b/crates/starknet_client/src/writer/objects/test_utils.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; +use papyrus_test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use starknet_api::core::{ClassHash, ContractAddress}; use starknet_api::deprecated_contract_class::{ ContractClassAbiEntry as DeprecatedContractClassAbiEntry, @@ -7,7 +8,6 @@ use starknet_api::deprecated_contract_class::{ EntryPointType as DeprecatedEntryPointType, }; use starknet_api::transaction::TransactionHash; -use test_utils::{auto_impl_get_test_instance, get_number_of_variants, GetTestInstance}; use crate::writer::objects::response::{ DeclareResponse, diff --git a/crates/starknet_client/src/writer/objects/transaction_test.rs b/crates/starknet_client/src/writer/objects/transaction_test.rs index af12e7d0ca9..61054f3cd74 100644 --- a/crates/starknet_client/src/writer/objects/transaction_test.rs +++ b/crates/starknet_client/src/writer/objects/transaction_test.rs @@ -1,4 +1,4 @@ -use test_utils::validate_load_and_dump; +use papyrus_test_utils::validate_load_and_dump; use super::{ DeclareV1Transaction, diff --git a/crates/starknet_client/src/writer/starknet_gateway_client_test.rs b/crates/starknet_client/src/writer/starknet_gateway_client_test.rs index 37f3e4e7b40..d08538518d7 100644 --- a/crates/starknet_client/src/writer/starknet_gateway_client_test.rs +++ b/crates/starknet_client/src/writer/starknet_gateway_client_test.rs @@ -2,8 +2,8 @@ use std::fmt::Debug; use std::future::Future; use mockito::{mock, Matcher}; +use papyrus_test_utils::read_json_file; use serde::{Deserialize, Serialize}; -use test_utils::read_json_file; use crate::test_utils::retry::get_test_config; use crate::writer::{StarknetGatewayClient, StarknetWriter, WriterClientError, WriterClientResult}; diff --git a/crates/starknet_sierra_compile/Cargo.toml b/crates/starknet_sierra_compile/Cargo.toml new file mode 100644 index 00000000000..7c21f0e417b --- /dev/null +++ b/crates/starknet_sierra_compile/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "starknet_sierra_compile" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +cairo-lang-sierra.workspace = true +cairo-lang-starknet-classes.workspace = true +cairo-lang-utils.workspace = true +serde_json.workspace = true +serde.workspace = true +thiserror.workspace = true + +[dev-dependencies] +assert_matches.workspace = true diff --git a/crates/starknet_sierra_compile/src/compile.rs b/crates/starknet_sierra_compile/src/compile.rs new file mode 100644 index 00000000000..f51eb68dfe3 --- /dev/null +++ b/crates/starknet_sierra_compile/src/compile.rs @@ -0,0 +1,43 @@ +use cairo_lang_starknet_classes::allowed_libfuncs::{AllowedLibfuncsError, ListSelector}; +use cairo_lang_starknet_classes::casm_contract_class::{ + CasmContractClass, + StarknetSierraCompilationError, +}; +use cairo_lang_starknet_classes::contract_class::ContractClass; +use thiserror::Error; + +#[cfg(test)] +#[path = "compile_test.rs"] +pub mod compile_test; +pub struct SierraToCasmCompilationArgs { + list_selector: ListSelector, + add_pythonic_hints: bool, + max_bytecode_size: usize, +} + +#[derive(Debug, Error)] +pub enum CompilationUtilError { + #[error(transparent)] + AllowedLibfuncsError(#[from] AllowedLibfuncsError), + #[error(transparent)] + StarknetSierraCompilationError(#[from] StarknetSierraCompilationError), +} + +/// This function may panic. +pub fn compile_sierra_to_casm( + contract_class: ContractClass, +) -> Result { + let compilation_args = SierraToCasmCompilationArgs { + list_selector: ListSelector::DefaultList, + add_pythonic_hints: true, + max_bytecode_size: 1000000, + }; + + contract_class.validate_version_compatible(compilation_args.list_selector)?; + + Ok(CasmContractClass::from_contract_class( + contract_class, + compilation_args.add_pythonic_hints, + compilation_args.max_bytecode_size, + )?) +} diff --git a/crates/starknet_sierra_compile/src/compile_test.rs b/crates/starknet_sierra_compile/src/compile_test.rs new file mode 100644 index 00000000000..37d1f3739e5 --- /dev/null +++ b/crates/starknet_sierra_compile/src/compile_test.rs @@ -0,0 +1,38 @@ +use std::path::Path; + +use assert_matches::assert_matches; +use cairo_lang_starknet_classes::allowed_libfuncs::AllowedLibfuncsError; + +use crate::compile::{compile_sierra_to_casm, CompilationUtilError}; +use crate::test_utils::contract_class_from_file; + +const FAULTY_ACCOUNT_SIERRA_FILE: &str = "account_faulty.sierra.json"; +const TEST_FILES_FOLDER: &str = "./tests/fixtures"; + +#[test] +fn test_compile_sierra_to_casm() { + let sierra_path = &Path::new(TEST_FILES_FOLDER).join(FAULTY_ACCOUNT_SIERRA_FILE); + let expected_casm_contract_length = 72304; + + let contract_class = contract_class_from_file(sierra_path); + let casm_contract = compile_sierra_to_casm(contract_class).unwrap(); + let serialized_casm = serde_json::to_string_pretty(&casm_contract).unwrap().into_bytes(); + + assert_eq!(serialized_casm.len(), expected_casm_contract_length); +} + +// TODO(Arni, 1/5/2024): Add a test for panic result test. +#[test] +fn test_negative_flow_compile_sierra_to_casm() { + let sierra_path = &Path::new(TEST_FILES_FOLDER).join(FAULTY_ACCOUNT_SIERRA_FILE); + + let mut contract_class = contract_class_from_file(sierra_path); + // Truncate the sierra program to trigger an error. + contract_class.sierra_program = contract_class.sierra_program[..100].to_vec(); + + let result = compile_sierra_to_casm(contract_class); + assert_matches!( + result, + Err(CompilationUtilError::AllowedLibfuncsError(AllowedLibfuncsError::SierraProgramError)) + ); +} diff --git a/crates/starknet_sierra_compile/src/lib.rs b/crates/starknet_sierra_compile/src/lib.rs new file mode 100644 index 00000000000..796bddbf84a --- /dev/null +++ b/crates/starknet_sierra_compile/src/lib.rs @@ -0,0 +1,6 @@ +//! A lib for compiling Sierra into Casm. + +pub mod compile; + +#[cfg(any(feature = "testing", test))] +pub mod test_utils; diff --git a/crates/starknet_sierra_compile/src/test_utils.rs b/crates/starknet_sierra_compile/src/test_utils.rs new file mode 100644 index 00000000000..dc0629d3391 --- /dev/null +++ b/crates/starknet_sierra_compile/src/test_utils.rs @@ -0,0 +1,33 @@ +use std::fs; +use std::path::Path; + +use cairo_lang_starknet_classes::contract_class::{ContractClass, ContractEntryPoints}; +use cairo_lang_utils::bigint::BigUintAsHex; +use serde::Deserialize; + +/// Same as `ContractClass` - but ignores unnecessary fields like `abi` in deserialization. +#[derive(Deserialize)] +struct DeserializedContractClass { + pub sierra_program: Vec, + pub sierra_program_debug_info: Option, + pub contract_class_version: String, + pub entry_points_by_type: ContractEntryPoints, +} + +pub(crate) fn contract_class_from_file(file: &Path) -> ContractClass { + let DeserializedContractClass { + sierra_program, + sierra_program_debug_info, + contract_class_version, + entry_points_by_type, + } = serde_json::from_str(&fs::read_to_string(file).expect("Failed to read input file.")) + .expect("deserialization Failed."); + + ContractClass { + sierra_program, + sierra_program_debug_info, + contract_class_version, + entry_points_by_type, + abi: None, + } +} diff --git a/crates/starknet_sierra_compile/tests/fixtures/account_faulty.sierra.json b/crates/starknet_sierra_compile/tests/fixtures/account_faulty.sierra.json new file mode 100644 index 00000000000..e832b549efd --- /dev/null +++ b/crates/starknet_sierra_compile/tests/fixtures/account_faulty.sierra.json @@ -0,0 +1,1224 @@ +{ + "sierra_program": [ + "0x1", + "0x4", + "0x0", + "0x2", + "0x4", + "0x3", + "0x1de", + "0x22", + "0x35", + "0x52616e6765436865636b", + "0x800000000000000100000000000000000000000000000000", + "0x426f78", + "0x800000000000000700000000000000000000000000000001", + "0x1", + "0xa", + "0x4172726179", + "0x800000000000000300000000000000000000000000000001", + "0x9", + "0x456e756d", + "0x800000000000000300000000000000000000000000000003", + "0x0", + "0xfeece2ea7edbbbebeeb5f270b77f64c680a68a089b794478dd9eca75e0196a", + "0x2", + "0x753634", + "0x800000000000000700000000000000000000000000000000", + "0x436f6e747261637441646472657373", + "0x537472756374", + "0x800000000000000700000000000000000000000000000004", + "0x3808c701a5d13e100ab11b6c02f91f752ecae7e420d21b56c90ec0a475cc7e5", + "0x4", + "0x5", + "0x6", + "0x1d", + "0x66656c74323532", + "0x800000000000000700000000000000000000000000000006", + "0x7d4d99e9ed8d285b5c61b493cedb63976bc3d9da867933d829f49ce838b5e7", + "0x7", + "0x8", + "0x800000000000000700000000000000000000000000000002", + "0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3", + "0x800000000000000f00000000000000000000000000000001", + "0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672", + "0xc", + "0x2ca39cde64b91db1514d78c135ee79d71b3b57fffee52f1a3ef96618a34d8c8", + "0xb", + "0xd", + "0x90d0203c41ad646d024845257a6eceb2f8b59b29ce7420dd518053d2edeedc", + "0x536e617073686f74", + "0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62", + "0x10", + "0x161ee0e6962e56453b5d68e09d1cabe5633858c1ba3a7e73fee8c70867eced0", + "0x11", + "0x4e6f6e5a65726f", + "0x14", + "0x3ae40d407f8074730e48241717c3dd78b7128d346cf81094e31806a3a5bdf", + "0x15", + "0x1b", + "0x17", + "0x1597b831feeb60c71f259624b79cf66995ea4f7e383403583674ab9c33b9cec", + "0x18", + "0x75313238", + "0x3342418ef16b3e2799b906b1e4e89dbb9b111332dd44f72458ce44f9895b508", + "0x1a", + "0x753332", + "0x80000000000000070000000000000000000000000000000e", + "0x348a62b7a38c0673e61e888d83a3ac1bf334ee7361a8514593d3d9532ed8b39", + "0x19", + "0x1c", + "0xa36a0a15af8cf1727a3a4fd9137671f23256b1f42299af56605a6910c522ce", + "0x1e", + "0x800000000000000700000000000000000000000000000003", + "0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7", + "0x20", + "0x29a02530bac70a6d5878fc0c5cb42f1926cd91d9162685c15f1be12819caf78", + "0x800000000000000f00000000000000000000000000000003", + "0x22", + "0xab150493414b66aee0a7b751a581ec565ab30639d18606b5bc9ef09ebaad6f", + "0x23", + "0x17b6ecc31946835b0d9d92c2dd7a9c14f29af0371571ae74a1b228828b2242", + "0x25", + "0x34f9bd7c6cb2dd4263175964ad75f1ff1461ddc332fbfb274e0fb2a5d7ab968", + "0x26", + "0x3d37ad6eafb32512d2dd95a2917f6bf14858de22c27a1114392429f2e5c15d7", + "0x556e696e697469616c697a6564", + "0x800000000000000200000000000000000000000000000001", + "0x3288d594b9a45d15bb2fcb7903f06cdb06b27f0ba88186ec4cfaa98307cb972", + "0x2d7b9ba5597ffc180f5bbd030da76b84ecf1e4f1311043a0a15295f29ccc1b0", + "0x2b", + "0x10203be321c62a7bd4c060d69539c1fbe065baa9e253c74d2cc48be163e259", + "0x2e", + "0x4275696c74696e436f737473", + "0x53797374656d", + "0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6", + "0x2d", + "0x11c6d8087e00642489f92d2821ad6ebd6532ad1a3b6d12833da6d6810391511", + "0x4761734275696c74696e", + "0xc9", + "0x7265766f6b655f61705f747261636b696e67", + "0x77697468647261775f676173", + "0x6272616e63685f616c69676e", + "0x73746f72655f74656d70", + "0x66756e6374696f6e5f63616c6c", + "0x3", + "0x656e756d5f6d61746368", + "0x33", + "0x64726f70", + "0x7374727563745f6465636f6e737472756374", + "0x61727261795f6c656e", + "0x736e617073686f745f74616b65", + "0x7533325f636f6e7374", + "0x72656e616d65", + "0x7533325f6571", + "0x61727261795f6e6577", + "0x66656c743235325f636f6e7374", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x61727261795f617070656e64", + "0x7374727563745f636f6e737472756374", + "0x656e756d5f696e6974", + "0x32", + "0x34", + "0x31", + "0x6765745f6275696c74696e5f636f737473", + "0x30", + "0x77697468647261775f6761735f616c6c", + "0x2f", + "0x4f7574206f6620676173", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", + "0x2c", + "0x4661696c656420746f20646573657269616c697a6520706172616d202333", + "0x4661696c656420746f20646573657269616c697a6520706172616d202332", + "0x616c6c6f635f6c6f63616c", + "0x66696e616c697a655f6c6f63616c73", + "0x28", + "0x73746f72655f6c6f63616c", + "0x27", + "0x29", + "0x2a", + "0xe", + "0xf", + "0x24", + "0x61727261795f736e617073686f745f706f705f66726f6e74", + "0x21", + "0x6a756d70", + "0x756e626f78", + "0x1f", + "0x12", + "0x16", + "0x647570", + "0x66656c743235325f737562", + "0x66656c743235325f69735f7a65726f", + "0x56414c4944", + "0x13", + "0x494e56414c4944", + "0x496e76616c6964207363656e6172696f", + "0x21adb5788e32c84f69a1863d85ef9394b7bf761a0ce1190f826984e5075c371", + "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", + "0x63616c6c5f636f6e74726163745f73797363616c6c", + "0x4f7074696f6e3a3a756e77726170206661696c65642e", + "0x7536345f636f6e7374", + "0x6765745f626c6f636b5f686173685f73797363616c6c", + "0x7536345f746f5f66656c74323532", + "0x636f6e74726163745f616464726573735f746f5f66656c74323532", + "0x53455155454e4345525f4d49534d41544348", + "0x424c4f434b5f54494d455354414d505f4d49534d41544348", + "0x424c4f434b5f4e554d4245525f4d49534d41544348", + "0x556e6b6e6f776e207363656e6172696f", + "0x626f6f6c5f6e6f745f696d706c", + "0x64697361626c655f61705f747261636b696e67", + "0x73656e645f6d6573736167655f746f5f6c315f73797363616c6c", + "0x61727261795f676574", + "0x496e646578206f7574206f6620626f756e6473", + "0x6765745f657865637574696f6e5f696e666f5f76325f73797363616c6c", + "0x6fb", + "0xffffffffffffffff", + "0x61", + "0x52", + "0x45", + "0x3e", + "0x36", + "0x37", + "0x38", + "0x39", + "0x3a", + "0x3b", + "0x3c", + "0x3d", + "0x3f", + "0x40", + "0x41", + "0x42", + "0x43", + "0x104", + "0xf5", + "0xe5", + "0xd4", + "0x9a", + "0xc4", + "0xbd", + "0x44", + "0x46", + "0x47", + "0x48", + "0x49", + "0x4a", + "0x4b", + "0x4c", + "0x4d", + "0x4e", + "0x4f", + "0x50", + "0x51", + "0x53", + "0x54", + "0x55", + "0x56", + "0x57", + "0x58", + "0x59", + "0x5a", + "0x1bd", + "0x1ac", + "0x19b", + "0x192", + "0x181", + "0x147", + "0x171", + "0x16a", + "0x5b", + "0x5c", + "0x5d", + "0x5e", + "0x5f", + "0x60", + "0x62", + "0x63", + "0x26d", + "0x25c", + "0x24b", + "0x242", + "0x231", + "0x202", + "0x221", + "0x2bb", + "0x299", + "0x2ae", + "0x327", + "0x318", + "0x2ea", + "0x30a", + "0x303", + "0x33c", + "0x341", + "0x34b", + "0x511", + "0x509", + "0x64", + "0x65", + "0x66", + "0x67", + "0x385", + "0x68", + "0x69", + "0x6a", + "0x6b", + "0x6c", + "0x6d", + "0x3b2", + "0x3a4", + "0x6e", + "0x6f", + "0x70", + "0x71", + "0x408", + "0x72", + "0x401", + "0x73", + "0x3f3", + "0x74", + "0x75", + "0x3db", + "0x76", + "0x77", + "0x3e0", + "0x78", + "0x79", + "0x7a", + "0x3ec", + "0x7b", + "0x7c", + "0x7d", + "0x433", + "0x7e", + "0x7f", + "0x80", + "0x41b", + "0x81", + "0x82", + "0x420", + "0x83", + "0x84", + "0x42c", + "0x85", + "0x86", + "0x87", + "0x88", + "0x89", + "0x8a", + "0x8b", + "0x8c", + "0x8d", + "0x8e", + "0x8f", + "0x90", + "0x4fa", + "0x91", + "0x92", + "0x93", + "0x94", + "0x95", + "0x96", + "0x97", + "0x98", + "0x99", + "0x4f2", + "0x9b", + "0x9c", + "0x9d", + "0x9e", + "0x9f", + "0xa0", + "0xa1", + "0xa2", + "0xa3", + "0xa4", + "0x4e9", + "0xa5", + "0xa6", + "0xa7", + "0xa8", + "0xa9", + "0xaa", + "0xab", + "0xac", + "0xad", + "0xae", + "0xaf", + "0x4e0", + "0xb0", + "0xb1", + "0xb2", + "0xb3", + "0xb4", + "0xb5", + "0xb6", + "0x4d6", + "0xb7", + "0xb8", + "0xb9", + "0xba", + "0xbb", + "0xbc", + "0xbe", + "0xbf", + "0xc0", + "0xc1", + "0xc2", + "0xc3", + "0xc5", + "0xc6", + "0xc7", + "0xc8", + "0xca", + "0xcb", + "0x4c4", + "0xcc", + "0xcd", + "0xce", + "0xcf", + "0xd0", + "0xd1", + "0xd2", + "0xd3", + "0xd5", + "0x4b4", + "0xd6", + "0xd7", + "0xd8", + "0xd9", + "0xda", + "0xdb", + "0xdc", + "0xdd", + "0xde", + "0xdf", + "0x4a6", + "0xe0", + "0xe1", + "0xe2", + "0xe3", + "0xe4", + "0xe6", + "0xe7", + "0xe8", + "0xe9", + "0xea", + "0xeb", + "0xec", + "0xed", + "0xee", + "0xef", + "0xf0", + "0xf1", + "0xf2", + "0xf3", + "0xf4", + "0xf6", + "0xf7", + "0xf8", + "0xf9", + "0xfa", + "0xfb", + "0xfc", + "0xfd", + "0xfe", + "0xff", + "0x100", + "0x101", + "0x524", + "0x529", + "0x54f", + "0x545", + "0x54a", + "0x567", + "0x56b", + "0x576", + "0x586", + "0x59f", + "0x598", + "0x5ad", + "0x5b2", + "0x5ce", + "0x5c8", + "0x5e6", + "0x5ea", + "0x5f8", + "0x60d", + "0x611", + "0x625", + "0x629", + "0x630", + "0x63c", + "0x641", + "0x65b", + "0x668", + "0x679", + "0x685", + "0x692", + "0x697", + "0x6a1", + "0x6e0", + "0x6b8", + "0x6d6", + "0x6d0", + "0x6f5", + "0x112", + "0x1cd", + "0x27d", + "0x2c9", + "0x335", + "0x352", + "0x518", + "0x51d", + "0x556", + "0x58d", + "0x5a5", + "0x5d8", + "0x5ff", + "0x614", + "0x617", + "0x649", + "0x661", + "0x673", + "0x67f", + "0x68b", + "0x6a7", + "0x6ef", + "0x3bfb", + "0x501813090402210078380a0502834180b050240a08038180a04018080200", + "0x142c050e81438050d83034120806430050200c2e050200c2c050a8142a05", + "0x900a230602c140e028880a1f0602c24210608024050287c181e090240a16", + "0x14560515030160a14814500c0f0481c050209c1c05048144c0c058284a05", + "0xc00a08038940a2f028b8180b050b40a1f06078240902810060902810580e", + "0x307012080dc6c050a81412051a8302612080d06605190303c12188140827", + "0xac0a3b028ec0a2b028d80a3a028240a09028240a2b028d80a16028240a39", + "0x488005168147e0c1f0283e0c100484a051e814780c058283a050f8303c12", + "0x1000a0e02918180b050940a4502910180b051000a430287c1842091041820", + "0x14984b200142c05250307c0a12814920524030160a2381456050f8301612", + "0xac0a1f0607824400293c0a4e060f81440029000a4d060f81416029309609", + "0x284a052a814a80c058280453081484a0528814a00c0582812050f8303c12", + "0x140a5c0b9780a5d158140a5c0616c185a06164b0022b9000a0902958183e", + "0x14ca3b02814c23b02814c80902814c62b02814c40902814c26002814be0c", + "0x140a6b048140a5c350380a69048140a680619c760502970760502998180e", + "0x1b80a052e1c00a052e1bc0a052e0141c6e02838da2502814d82202814d809", + "0x140a5c048140a64288140a623a0140a5f0e1780a5d061cce405029701871", + "0x38da5502814d82b02814d80e02814c20e02814c84002814c21d2f014ba0e", + "0x140a613b8140a5f049780a5d3b0380a69158140a613a8380a6906038dc05", + "0x14d27807014d2062f014ba4f02814b84302814c24302814c84302814d84f", + "0x140a7d0b0140a7d3e0140a5f121780a5d061ec1205029e82c05029e8f20e", + "0x14ba1602814b81602814c24702814be4902814c47e02814be222f014ba09", + "0x18506050297c525e02975045e02975025e02975000502984fe05029844a5e", + "0x1000a05360310c8502814b82902814b80c072140a0e368240a05421140a05", + "0x140a5c06038c005071b4120502998120502a1d0a050297c0a0e428141c6d", + "0x600a05310600a05438f40a05312200a052f8acbc052e8141c6002838da60", + "0x140a62450140a5f449780a5d148140a66158140a641d0140a611b0140a61", + "0x301c7402838da5102814d88e07014d20c46831180902815160c07014d22f", + "0x1a40a0e3a0141c6d488380a69480380a69028380a69478140a613a0140a5c", + "0x38da8902814b80c072240a0e36831289307014d20c490141c05328381c05", + "0x380a962f0380a694a8380a692a8140a61370140a5f479780a5d028391205", + "0xb4bc052e8141c8202838da8202814b80c072080a0e368312e1502814b80c", + "0x140a62408140a5f179780a5d2f0380a65070380a650a8380a69288140a61", + "0x31301702814c417028150e1c02814b81d02814c20602814c406028150e24", + "0x389e05071b53a0e029a5380e029a42a0502985360e029a5340e029a41899", + "0x14c805071dc0a0e369dc0a052e0301c7702838da0c4f0301c4f02838da05", + "0x27c0a0e3e0141c6d3e0140a5c06038f805071b49e05029989e050297c9e05", + "0x38da05071f80a0e369f80a052e0301c7e02838da4902814d88a2f014ba0c", + "0x1840a0e418141c6d418140a5c060390605071b48a05029b018a0028388e05", + "0x240a05508141c8802838da8802814b80c072200a0e368f40a05360700a05", + "0x140a5f028391405071b5440e029a5140502970180e450141c6d178140a6c", + "0xc4bc052e8141c5e02838da5e02814b80c071780a0e36831468202814be89", + "0x17c180e238141c6d028390205071b5020502970180e408141c6d120140a6c", + "0x301c0c0e05c1ca60b0541ca507014180e0283018a502830180c521780a05", + "0x154a050a8142c0c048741ca5029780a15061780aa5029780a5e060314a05", + "0x74180c528140c050e03018a5028301c0c120154e0602a941c090285c1815", + "0x2040a2406209020e528144a05030304a05528144405048304405528143a05", + "0x3018a5028ac0a2406224560e528145205030305205528141822060314a05", + "0x23c1c820623c0aa502a3c0a81060b40aa502a240a250623c0aa502a080a25", + "0x31140552814182b060bc0aa502830520c062940a0c070301885062941c2d", + "0xcc1c2f060cc0aa5028305a0c188154a05450bc1c8f062280aa502a280a89", + "0x2940a16028c4181502a940a1502858183602a940a3a02a28183a02a940a31", + "0x301c0c1b0382c150a8146c05528146c051d0301c05528141c05198302c05", + "0x394a0e180582a5e1d8306005528146005180306005528141836060314a05", + "0x154a050c014620c1d8154a051d8142c0c062940a0c07031103d072a0303b", + "0x388a051e8308a43429002aa502838303b2f060180e02a940a0e028cc1818", + "0x1f80aa502a0c0a88061240aa502830520c062940a0c070308e0554a0c0aa5", + "0x150a0c3f8154a053f815120c062940a7c02870187f3e0394a053f014800c", + "0x1500054183018a50293c0a450613d000e52814927f0710c184902a940a49", + "0x1d00aa5029440a7e061440aa5029540a49060314a053b8148e0c2a9dc1ca5", + "0x14660c428154a0542814620c200154a05200142c0c390154a053a014f80c", + "0x228180c52814180e061c88685200540a7202a940a72028e8184302a940a43", + "0x148605198310a05528150a051883080055281480050b030e005528148e05", + "0x141829060314a0506038187021a148015029c00aa5029c00a3a0610c0aa5", + "0x1bc0aa502980dc0e47830c00552814c00544830c00552814187f061b80aa5", + "0x142c0c558154a0555015140c550154a05378001c2f060000aa5028305a0c", + "0x2940aab028e8180e02a940a0e028cc188802a940a88028c4183d02a940a3d", + "0x2940a1d02a00180c5281448052283018a5028301c0c55839103d0a8155605", + "0x2b01c8f062b40aa502ab40a89062b40aa5028309e0c560154a05060a4180c", + "0x2940ab002a2818b002a940aae578385e0c578154a05060b418ae02a940aad", + "0x301c05528141c05198302c05528142c05188302a05528142a050b0316205", + "0x3018a5029780a80060314a050603818b1070582a1502ac40aa502ac40a3a", + "0x1564a90723c18b202a940ab202a2418b202a940a0c3f8315205528141829", + "0x2d80aa502ad40a8a062d40aa502acd680e17831680552814182d062cc0aa5", + "0x14740c070154a0507014660c0e0154a050e014620c0b8154a050b8142c0c", + "0x582a0e528380a0c07014180c52814180c062d81c1c0b8540ab602a940ab6", + "0x243a0e52814bc050a830bc0552814bc052f03018a5028301c0c0e05c1cb7", + "0x178180c52814180e060900ab8030154a0e048142e0c0a8154a050a8142c0c", + "0x3104055ca040aa5070940a1706094440e528143a050a8303a05528143a05", + "0x3856052a8305629072940a22029dc182202a940a2202978180c52814180e", + "0x154a0516814120c168154a05148143a0c062940a0c070311e055d2240aa5", + "0x140c0c198154a0506088180c52815140512030628a072940a2f02818182f", + "0x2940a3602894183002a940a3102894180c528147405120306c3a072940a33", + "0x3018a5028301c0c062ec18a5070ec600e410306005528146005408307605", + "0x600aa502830520c062940a0602870180c5281502050e03018a502a240a51", + "0x305a0c440154a051e8601c8f060f40aa5028f40a89060f40aa502830560c", + "0x2940a1502858184302a940a8502a28188502a940a88200385e0c200154a05", + "0x1486055281486051d0301c05528141c05198302c05528142c05188302a05", + "0x308a05528148a05180308a05528141836060314a05060381843070582a15", + "0x1f00aa502830e80c062940a0c07030fc49072f08e83072941c450b054bc3b", + "0x14620c418154a05418142c0c062940a7f029c018803f8394a053e014e40c", + "0x2940a8102a24180602a940a0602a24180e02a940a0e028cc184702a940a47", + "0x144aa77278554a0544a040c800711d061c300311205528151205370310205", + "0x15100c380154a05060a4180c52814180e061c80abd3a0154a0e288147a0c", + "0x2940a6f02a24180c52814c0050e030de60072940a6e02900186e02a940a74", + "0x314a05550148a0c550001ca5029c0de0e21830e00552814e00542830de05", + "0x14fc0c568154a0556014920c062940aab0291c18ac558394a0500015060c", + "0x2940a77028c4184f02a940a4f0285818af02a940aae029f018ae02a940aad", + "0x301c0c57954ee4f0a8155e05528155e051d030aa0552814aa0519830ee05", + "0x1dc0aa5029dc0a310613c0aa50293c0a16062c00aa5029c80a8a060314a05", + "0x2940a0c0703160553b93c2a05580154a0558014740c2a8154a052a814660c", + "0x2940a0c1483018a5028180a1c060314a0540814380c062940a8902944180c", + "0x3164055281552b10723c18a902a940aa902a2418a902a940a0c3f8316205", + "0x1240a16062d40aa502ad00a8a062d00aa502ac9660e17831660552814182d", + "0x154a055a814740c070154a0507014660c3f0154a053f014620c248154a05", + "0x314a0514815000c062940a8f02914180c52814180e062d41c7e248540ab5", + "0x154a05061bc18b602a940a0c1483018a5028180a1c060314a0540814380c", + "0xbc18bf02a940a0c168315005528157cb60723c18be02a940abe02a2418be", + "0x580a31060540aa5028540a16063040aa502b000a8a063000aa502aa17e0e", + "0x31820e0b0542a05608154a0560814740c070154a0507014660c0b0154a05", + "0x3018a5028880a80060314a0503014380c062940a8202914180c52814180e", + "0x1586c20723c18c302a940ac302a2418c302a940a0c000318405528141829", + "0x31c0aa502b180a8a063180aa502b118a0e178318a0552814182d063100aa5", + "0x14740c070154a0507014660c0b0154a050b014620c0a8154a050a8142c0c", + "0x15000c062940a2402914180c52814180e0631c1c160a8540ac702a940ac7", + "0x31920552815920544831920552814184f063200aa502830520c062940a1d", + "0x15140c658154a056529c1c2f0629c0aa5028305a0c650154a0564b201c8f", + "0x2940a0e028cc181602a940a16028c4181502a940a150285818cc02a940acb", + "0x14bc054003018a5028301c0c660382c150a81598055281598051d0301c05", + "0x391e0c670154a0567015120c670154a05061fc18cd02a940a0c1483018a5", + "0x15a20545031a205528159ed0070bc18d002a940a0c168319e05528159ccd", + "0x380aa5028380a33060700aa5028700a310605c0aa50285c0a16063480aa5", + "0x154a05062ac181602a940a0c55031a40e0e05c2a05690154a0569014740c", + "0x900c0e698243a0e528380a0c07014180c52814180c060314a05062b0181c", + "0x1783a0e56830bc0552814bc052f0303a05528143a050b03018a5028301c0c", + "0x940a5e060314a0506038188202b502a05528390205570310225111794a05", + "0x3856050b8302a05528142a16072bc182b148394a05128142a0c128154a05", + "0x154a0504814620c110154a05110142c0c062940a0c0703112056a85c0aa5", + "0x1794a0514824445e588302e05528142e1c072c0182902a940a29029781809", + "0x394a0545015640c062940a0c0703062056b2280aa5070bc0aa9060bc5a8f", + "0xec0aa5028cc0a1d060314a0506038183002b5c6c05528387405598307433", + "0x30440c062940a3d0289018881e8394a050c0140c0c0c0154a051d814120c", + "0x154a05440144a0c062940a85028901843428394a05200140c0c200154a05", + "0x31b00c52839064507208184502a940a4502a04188302a940a43028941845", + "0x314a050a815680c062940a1702870180c528146c052383018a5028301c0c", + "0x1248e0e47830920552814920544830920552814182b0611c0aa502830520c", + "0x154a053f815140c3f8154a053f1f01c2f061f00aa5028305a0c3f0154a05", + "0xe8180e02a940a0e028cc182d02a940a2d028c4188f02a940a8f028581880", + "0xc0184f02a940a0c1b03018a5028301c0c400385a8f0a8150005528150005", + "0x314a0506038187428839b2553b8394a0e278b51e5e1d8309e05528149e05", + "0x1dc0a16060314a0538014e00c371c01ca5029c80a72061c80aa502830e80c", + "0x154a050a8156a0c070154a0507014660c2a8154a052a814620c3b8154a05", + "0xd82e1537038aa770e2d8183602a940a3602a14181702a940a1702a241815", + "0x30520c062940a0c0703158056d2ac0aa5072a80a3d062a8006f300554a05", + "0x2940aaf0287018b0578394a0557014800c570154a0555815100c568154a05", + "0x2a5620e528155ab00710c18ad02a940aad02a1418b002a940ab002a24180c", + "0x2cc0a49060314a05590148e0c59ac81ca502ac40a83060314a05548148a0c", + "0x154a05300142c0c5b0154a055a814f80c5a8154a055a014fc0c5a0154a05", + "0x540ab602a940ab6028e8180002a940a00028cc186f02a940a6f028c41860", + "0x30c00552814c0050b0317c055281558054503018a5028301c0c5b000de60", + "0x1bcc01502af80aa502af80a3a060000aa5028000a33061bc0aa5029bc0a31", + "0x142a055a03018a50285c0a1c060314a051b0148e0c062940a0c070317c00", + "0x391e0c5f8154a055f815120c5f8154a05061fc18a802a940a0c1483018a5", + "0x1584054503184055281580c1070bc18c102a940a0c168318005528157ea8", + "0x380aa5028380a33061d00aa5029d00a31061440aa5029440a160630c0aa5", + "0x314a05180148a0c062940a0c07031860e3a1442a05618154a0561814740c", + "0x154a05060a4180c528142a055a03018a50285c0a1c060314a0519815000c", + "0xb418c602a940ac5620391e0c628154a0562815120c628154a05061bc18c4", + "0x151e050b0319205528159005450319005528158cc7070bc18c702a940a0c", + "0x3240aa502b240a3a060380aa5028380a33060b40aa5028b40a310623c0aa5", + "0x3018a50285c0a1c060314a050a815680c062940a0c07031920e16a3c2a05", + "0x380a33060b40aa5028b40a310623c0aa502a3c0a16063280aa5028c40a8a", + "0x148a0c062940a0c07031940e16a3c2a05650154a0565014740c070154a05", + "0xa4180c5281438055f03018a5028a40a80060314a050a815680c062940a89", + "0x2940acb538391e0c658154a0565815120c658154a050600018a702a940a0c", + "0x319e05528159c05450319c055281598cd070bc18cd02a940a0c168319805", + "0x33c0a3a060380aa5028380a33060240aa5028240a31060880aa5028880a16", + "0x940a80060314a05410148a0c062940a0c070319e0e048882a05678154a05", + "0x309e0c680154a05060a4180c528142c055403018a5028700abe060314a05", + "0x154a05060b418d202a940ad1680391e0c688154a0568815120c688154a05", + "0x3044055281444050b031ba0552815b80545031b80552815a4db070bc18db", + "0x24441502b740aa502b740a3a060380aa5028380a33060240aa5028240a31", + "0x1438055f03018a5029780a80060314a050b015500c062940a0c07031ba0e", + "0x391e0c6f8154a056f815120c6f8154a05061fc18de02a940a0c1483018a5", + "0x15c20545031c20552815c0a6070bc18a602a940a0c16831c00552815bede", + "0x380aa5028380a33060900aa5028900a31060180aa5028180a16063880aa5", + "0x154a05062ac181602a940a0c55031c40e120182a05710154a0571014740c", + "0x900c0e718243a0e528380a0c07014180c52814180c060314a05062b0181c", + "0x1783a0e56830bc0552814bc052f0303a05528143a050b03018a5028301c0c", + "0x940a5e060314a0506038188202b902a05528390205570310225111794a05", + "0x3856050b8302a05528142a16072bc182b148394a05128142a0c128154a05", + "0x154a0504814620c110154a05110142c0c062940a0c0703112057285c0aa5", + "0x1794a0514824445e588302e05528142e1c072c0182902a940a29029781809", + "0x394a0545015640c062940a0c070306205732280aa5070bc0aa9060bc5a8f", + "0xec0aa5028cc0a1d060314a0506038183002b9c6c05528387405598307433", + "0x30440c062940a3d0289018881e8394a050c0140c0c0c0154a051d814120c", + "0x154a05440144a0c062940a85028901843428394a05200140c0c200154a05", + "0x31d00c52839064507208184502a940a4502a04188302a940a43028941845", + "0x314a050a815680c062940a1702870180c528146c052383018a5028301c0c", + "0x1248e0e47830920552814920544830920552814182b0611c0aa502830520c", + "0x154a053f815140c3f8154a053f1f01c2f061f00aa5028305a0c3f0154a05", + "0xe8180e02a940a0e028cc182d02a940a2d028c4188f02a940a8f028581880", + "0xc0184f02a940a0c1b03018a5028301c0c400385a8f0a8150005528150005", + "0x314a0506038187428839d2553b8394a0e278b51e5e1d8309e05528149e05", + "0x1540a31060314a0538014e00c371c01ca5029c80a72061c80aa502830e80c", + "0x154a050b815120c0a8154a050a8156a0c070154a0507014660c2a8154a05", + "0x30520c001bcc05e528146c170a9b81c550bafc183602a940a3602a141817", + "0x154a0556015120c062940aab0287018ac558394a0500014800c550154a05", + "0x3018a502ab80a45062b95a0e5281554ac0710c18aa02a940aaa02a1418ac", + "0x2c40a7e062c40aa502ac00a49060314a05578148e0c582bc1ca502ab40a83", + "0x154a0530014620c3b8154a053b8142c0c590154a0554814f80c548154a05", + "0x14180e062c8de603b8540ab202a940ab2028e8186f02a940a6f028cc1860", + "0x141829060314a050a815680c062940a1702870180c528146c052383018a5", + "0x2d40aa502ad1660e47831680552815680544831680552814187f062cc0aa5", + "0x142c0c540154a055f015140c5f0154a055aad81c2f062d80aa5028305a0c", + "0x2940aa8028e8180e02a940a0e028cc187402a940a74028c4185102a940a51", + "0x2940a3302a00180c5281460052283018a5028301c0c54038e8510a8155005", + "0x2940a0c378317e05528141829060314a050a815680c062940a1702870180c", + "0x31840552814182d063040aa502b017e0e478318005528158005448318005", + "0x14620c478154a05478142c0c620154a0561815140c618154a0560b081c2f", + "0x3101c2d478540ac402a940ac4028e8180e02a940a0e028cc182d02a940a2d", + "0x154a0518815140c062940a1702870180c528142a055a03018a5028301c0c", + "0xe8180e02a940a0e028cc182d02a940a2d028c4188f02a940a8f0285818c5", + "0x2d0180c5281512052283018a5028301c0c628385a8f0a8158a05528158a05", + "0x318c05528141829060314a050e0157c0c062940a2902a00180c528142a05", + "0x14182d063200aa502b1d8c0e478318e05528158e05448318e05528141800", + "0x154a05110142c0c538154a0565015140c650154a05643241c2f063240aa5", + "0x540aa702a940aa7028e8180e02a940a0e028cc180902a940a09028c41822", + "0x157c0c062940a2502a00180c5281504052283018a5028301c0c538381222", + "0x22418cc02a940a0c278319605528141829060314a050b015500c062940a1c", + "0x3359c0e178319c0552814182d063340aa502b31960e478319805528159805", + "0x154a0504814620c110154a05110142c0c680154a0567815140c678154a05", + "0x14180e063401c09110540ad002a940ad0028e8180e02a940a0e028cc1809", + "0x141829060314a050e0157c0c062940a5e02a00180c528142c055403018a5", + "0x36c0aa502b49a20e47831a40552815a40544831a40552814187f063440aa5", + "0x142c0c6f0154a056e815140c6e8154a056db701c2f063700aa5028305a0c", + "0x2940ade028e8180e02a940a0e028cc182402a940a24028c4180602a940a06", + "0x5c1cea0b0541ca507014180e0283018a502830180c6f03848060a815bc05", + "0x240a06060240aa5028740a09060740aa5029780a1d060314a0506038181c", + "0x941ca5028880a06060880aa502830440c062940a06028901824030394a05", + "0x15020c148154a05408144a0c410154a05120144a0c062940a25028901881", + "0x14180e06031d60c52838528207208181502a940a1502858188202a940a82", + "0x391e0c448154a0544815120c448154a05060ac182b02a940a0c1483018a5", + "0x145e05450305e05528151e2d070bc182d02a940a0c168311e0552815122b", + "0x380aa5028380a33060580aa5028580a31060540aa5028540a16062280aa5", + "0xc40aa5028306c0c062940a0c07031140e0b0542a05450154a0545014740c", + "0x14180e060c06c0e760e8660e5283862160a978760c188154a0518814600c", + "0x300180c528143005380307a18072940a3b029c8183b02a940a0c3a03018a5", + "0x394a0520015060c200154a05060a4180c528151005228311005528147a05", + "0x1f0188302a940a45029f8184502a940a4302924180c528150a05238308685", + "0x141c051983074055281474051883066055281466050b0308e05528150605", + "0x141829060314a05060381847070e866150291c0aa50291c0a3a060380aa5", + "0x1f00aa5029f8920e47830fc0552814fc0544830fc0552814187f061240aa5", + "0x142c0c278154a0540015140c400154a053e1fc1c2f061fc0aa5028305a0c", + "0x2940a4f028e8180e02a940a0e028cc183002a940a30028c4183602a940a36", + "0x154a05060a4180c52814bc054003018a5028301c0c2783860360a8149e05", + "0xb4185102a940a553b8391e0c2a8154a052a815120c2a8154a05061fc1877", + "0x142e050b030e00552814e40545030e40552814a274070bc187402a940a0c", + "0x1c00aa5029c00a3a060380aa5028380a33060700aa5028700a310605c0aa5", + "0x303817073b42c15072941c05060380a0c062940a0c06030e00e0e05c2a05", + "0x142a050b030121d072940a5e029dc185e02a940a5e02978180c52814180e", + "0x154a050e8143a0c062940a0c070304805770180aa5070240a55060540aa5", + "0x88180c528150205120310481072940a2502818182502a940a22028241822", + "0x2940a8202894180c52814560512031122b072940a2902818182902a940a0c", + "0x3bc18a5070b51e0e410311e05528151e05408305a05528151205128311e05", + "0x154a05060ac182f02a940a0c1483018a5028180a51060314a0506038180c", + "0xbc183302a940a0c16830620552815142f0723c188a02a940a8a02a24188a", + "0x580a31060540aa5028540a16060d80aa5028e80a8a060e80aa5028c4660e", + "0x306c0e0b0542a051b0154a051b014740c070154a0507014660c0b0154a05", + "0x3860160a978760c180154a0518014600c180154a05060d8180c52814180e", + "0x2940a3b02858184002a940a0c3a03018a5028301c0c440f41cf00c0ec1ca5", + "0x300c05528140c05370301c05528141c05198303005528143005188307605", + "0x38184902bc48e0552839060561031064521a142aa502818800e0c0ec2cc1", + "0x1fcf80e52814fc0541830fc05528141829060314a0523815860c062940a0c", + "0x13c0a7c0613c0aa502a000a7e062000aa5029fc0a49060314a053e0148e0c", + "0x154a0522814660c218154a0521814620c428154a05428142c0c3b8154a05", + "0x2940a4902a28180c52814180e061dc8a43428540a7702a940a77028e81845", + "0x308a05528148a05198308605528148605188310a05528150a050b030aa05", + "0x3018a5028180a51060314a050603818552290d0a15029540aa5029540a3a", + "0x14e8510723c187402a940a7402a24187402a940a0c3f830a205528141829", + "0x1800aa5029b80a8a061b80aa5029c8e00e17830e00552814182d061c80aa5", + "0x14740c070154a0507014660c440154a0544014620c1e8154a051e8142c0c", + "0x15000c062940a2402914180c52814180e061801c881e8540a6002a940a60", + "0x30000552814000544830000552814184f061bc0aa502830520c062940a1d", + "0x15140c560154a05552ac1c2f062ac0aa5028305a0c550154a05001bc1c8f", + "0x2940a0e028cc181602a940a16028c4181502a940a150285818ad02a940aac", + "0x14bc054003018a5028301c0c568382c150a8155a05528155a051d0301c05", + "0x391e0c578154a0557815120c578154a05061fc18ae02a940a0c1483018a5", + "0x1552054503152055281560b1070bc18b102a940a0c168316005528155eae", + "0x380aa5028380a33060700aa5028700a310605c0aa50285c0a16062c80aa5", + "0x380a05620300a055281418050e831640e0e05c2a05590154a0559014740c", + "0x2940a0e02b18181602a940a5e02b14180c52814180e060540af22f0381ca5", + "0x2940a0c6483018a5028301c0c063cc0a0c640303805528142c05638302e05", + "0x303805528141205638302e05528142a05630301205528143a05650303a05", + "0x3044057a0900aa5070700aa7060180aa5028180a5e060180aa50285c0a49", + "0x2940a8102b34188102a940a2502b30182502a940a2402b2c180c52814180e", + "0x2940a0c070310406070150405528150405670300c05528140c052f0310405", + "0x180a5e060ac0aa5028a40acf060a40aa502831920c062940a2202914180c", + "0x14660c028154a0502814620c158181c05158154a05158159c0c030154a05", + "0x700af50b8154a0e0b015a20c0b054bc5e528141c0507340180e02a940a0e", + "0x1412056e0301205528143a056d8303a05528142e056903018a5028301c0c", + "0x1448055a03018a5028180a1c062285e2d47a245629412044a22120184aa5", + "0xa40a1c060314a0541014380c062940a8102870180c5281444056e83018a5", + "0x14480c062940a8f02a00180c5281512056e83018a5028ac0ade060314a05", + "0xcc620e528144a056f83018a502a280a80060314a0517814480c062940a2d", + "0x300a16060c00aa5028d80ae0060d80aa5028cc0a1d060e80aa502830440c", + "0x147430061794c0c1d0154a051d015020c180154a05180158c0c060154a05", + "0x14180e062200af61e8154a0e0c015c20c188154a0518814bc0c0c0ec1ca5", + "0x308605528150a05660310a05528148005658308005528147a057103018a5", + "0x20c1cf90620c860e5281486057c0308605528148605448308a055281418f7", + "0x14180e061240afb062941c4702be8184702a940a4702a24184702a940a45", + "0x1f80afd061f80aa502831f80c062940a4302870180c5281462054003018a5", + "0x154a052f014620c1d8154a051d8142c0c3f8154a053e015fc0c3e0154a05", + "0x14180e061fc2a5e1d8540a7f02a940a7f02bfc181502a940a15028cc185e", + "0x3e4184f218394a0521815f00c400154a0506404180c5281492058003018a5", + "0x38185502c0818a5071dc0afa061dc0aa5029dc0a89061dc0aa502a009e0e", + "0x100185102a940a0c7b83018a50290c0a1c060314a0518815000c062940a0c", + "0x2940a7002900187002a940a0c8083018a5029d00a1c061c8e80e52814a205", + "0x30000552814c00566030de0552814e4056603018a5029b80a1c06180dc0e", + "0x15f40c550154a0555015120c550154a05001bc1cf9061bc0aa5029bc0a89", + "0x154a0556015fa0c560154a0506410180c52814180e062ac0b03062941caa", + "0xcc185e02a940a5e028c4183b02a940a3b0285818ae02a940aad02bf818ad", + "0x3018a5028301c0c57054bc3b0a8155c05528155c057f8302a05528142a05", + "0x154a0558015120c580154a050641418af02a940a0c1483018a502aac0b00", + "0x3164055281562a9070bc18a902a940a0c1683162055281560af0723c18b0", + "0x540a33061780aa5029780a31060ec0aa5028ec0a16062cc0aa502ac80b06", + "0x16000c062940a0c0703166152f0ec2a05598154a0559815fe0c0a8154a05", + "0x2940ab45a839f20c5a90c1ca50290c0af8062d00aa5028320e0c062940a55", + "0x3018a5028301c0c5f016100c528396c057d0316c05528156c05448316c05", + "0x2940a0c8483018a502aa00a80062fd500e5281462056f83018a50290c0a1c", + "0x3076055281476050b0318405528158205700318205528157e050e8318005", + "0x3188c3072940ac0610ecbca6063000aa502b000a81063080aa502b080ac6", + "0x15960c638154a0562815c40c062940a0c070318c05853140aa5073100ae1", + "0x154a0565015120c650154a0564015980c648154a05060a418c802a940ac7", + "0x394a0564815060c062940a0c0703198058632d4e0e5283994c30742c18ca", + "0x15120c680154a0567014920c678154a0506434180c528159a05238319ccd", + "0x32c2a5e0b43818a702a940aa70285818d002a940ad00297818cf02a940acf", + "0x37c0aa502b6c0b10060314a050603818de6eb70bd0f6db49a25e52839a0cf", + "0x31900c708154a056f816220c530154a0569014660c700154a0568814620c", + "0xcc18e002a940adc028c418e202a940ade02c4c180c52814180e060322405", + "0x39ee058a831ee0552815c2058a031c20552815c405888314c0552815ba05", + "0x31f4055281418fc060314a057c0162e0c062940a0c07031f2058b3e00aa5", + "0x3800a310629c0aa502a9c0a16063f40aa502bf00afe063f00aa502be80afd", + "0x31faa67029c2a057e8154a057e815fe0c530154a0553014660c700154a05", + "0x2940ae0028c418a702a940aa70285818fe02a940af902c18180c52814180e", + "0x301c0c7f299c0a70a815fc0552815fc057f8314c05528154c0519831c005", + "0x15120c800154a050646018ff02a940a0c1483018a502b240a47060314a05", + "0x160304070bc190402a940a0c1683202055281600ff0723c190002a940b00", + "0x1780aa5029780a31063300aa502b300a16064180aa502c140b06064140aa5", + "0x2940a0c070320c152f3302a05830154a0583015fe0c0a8154a050a814660c", + "0xcc185e02a940a5e028c418c302a940ac302858190702a940ac602c18180c", + "0x3018a5028301c0c83854bcc30a8160e05528160e057f8302a05528142a05", + "0x426160e7c8321643072940a4302be0190902a940a0c8c83018a502af80b00", + "0x2940a0c070321c058d0314a0e86815f40c868154a0586815120c868154a05", + "0x1620058e032200552814191b060314a0521814380c062940a3102a00180c", + "0x3018a5028301c0c8c45e2a5e8f45227112f2941d100a978bd1d064400aa5", + "0x4640b20064700aa502c4c0a330646c0aa502c440a31064640aa502c500b1f", + "0x14620c8f8154a058c016440c062940a0c07030192102831900c8e8154a05", + "0x2940b1d02c8c191d02a940b1f02c80191c02a940b17028cc191b02a940b15", + "0x3018a502c880b25060314a0506038192302c9244055283a40051e8324005", + "0x1476050b0324e05528164c057f0324c05528164a057e8324a055281418fc", + "0x49c0aa502c9c0aff064700aa502c700a330646c0aa502c6c0a31060ec0aa5", + "0x154a051d8142c0c940154a05918160c0c062940a0c070324f1c8d8ec2a05", + "0x540b2802a940b2802bfc191c02a940b1c028cc191b02a940b1b028c4183b", + "0x325529072940a4302900180c528161c058003018a5028301c0c94472363b", + "0x1658050e0325b2c072940b2b02900192b02a940a0c9303018a502ca40a1c", + "0x4c00aa502cbe5c0e7c8325e05528165a05660325c055281654056603018a5", + "0x15be0c062940a0c070326405988314a0e98015f40c980154a0598015120c", + "0x2940b3602b80193602a940b3402874193502a940a0c848326933072940a31", + "0x326a05528166a05408326e05528166e056303076055281476050b0326e05", + "0x314a050603818bb02cee74055283a7205708327338072940b359b8ecbca6", + "0x49c193f9f0394a0599815be0c9e8154a059e015960c9e0154a059d015c40c", + "0x2940b3802858194202a940b4102b80194102a940b3f02874194002a940a0c", + "0x394a05a050a705e530328005528168005408328405528168405630327005", + "0x314a0506038194702d1a8a055283a8805708327a05528167a05448328943", + "0x200194ba50394a059f015be0ca48154a05a4015960ca40154a05a2815c40c", + "0x154a05a6815c00ca68154a05a58143a0ca60154a05064a0180c528169405", + "0x298194c02a940b4c02a04194e02a940b4e02b18194302a940b4302858194e", + "0x16a55102a941d5002b84194902a940b4902a241950a78394a05a653a865e", + "0x1780a31065540aa502d500acb065500aa502d440ae2060314a05060381953", + "0x5540a8906562af562f2940a152f03a520c0a8154a050a814660c2f0154a05", + "0x2940b5902cac180c52814180e0656c0b5aac8154a0eac016540caa8154a05", + "0x16be0597032c2bdb057ebc1652816ba0596832ba0552816b80596032b805", + "0x5780b2f060314a05b0814380c062940abd02ad0180c52816c0055a03018a5", + "0x16c60599832cb64b19794a05b1016640cb10154a05af016600caf0154a05", + "0x314a05b3814380cb459c1ca502d980a40062f00aa502cf40acc065980aa5", + "0x15980c2c0154a05b4015980c062940b6902870196ab48394a055e014800c", + "0x16d80544832d80552816d658073e4185802a940a5802a24196b02a940b6a", + "0x5b418a5075b00afa065940aa502d940ab5065900aa502d900b1c065b00aa5", + "0x14800cb80154a05a4815980cb78154a05b2016660c062940a0c07032dc05", + "0x16e6050e032e973072940b7002900180c52816e2050e032e571072940b6f", + "0x5d80aa502aeaea0e7c831740552816e80566032ea0552816e4056603018a5", + "0x16680c062940a0c07032f005bb8314a0ebb015f40cbb0154a05bb015120c", + "0x16f6050e032f97b072940b7902900197a02a940b5502b30197902a940b65", + "0x32fe0552816f8056603018a502df40a1c065fafa0e52816f4052003018a5", + "0x15f40cc08154a05c0815120cc08154a05c05fc1cf9066000aa502df80acc", + "0x154a05c2015fa0cc20154a05063f0180c52814180e0660c0b82062941d81", + "0xcc195602a940b56028c4194f02a940b4f02858198602a940b8502bf81985", + "0x3018a5028301c0cc355ead4f0a8170c05528170c057f832ae0552816ae05", + "0x154a055c815120c5c8154a05064d4198702a940a0c1483018a502e0c0b00", + "0x331405528171189070bc198902a940a0c1683310055281573870723c18b9", + "0x55c0a33065580aa502d580a310653c0aa502d3c0a160662c0aa502e280b06", + "0x16000c062940a0c070331757ab53c2a05c58154a05c5815fe0cab8154a05", + "0x4d8198c02a940a0c1483018a502d940ab4060314a05aa814380c062940b78", + "0x2940a0c168331c05528171b8c0723c198d02a940b8d02a24198d02a940a0c", + "0x53c0aa502d3c0a16066440aa502e400b06066400aa502e3b1e0e178331e05", + "0x53c2a05c88154a05c8815fe0cab8154a05ab814660cab0154a05ab014620c", + "0x5940ab4060314a05aa814380c062940b6e02c00180c52814180e06646af56", + "0x32700cc90154a05060a4180c52816c8059b83018a502d240a1c060314a05", + "0x154a05060b4199402a940b93c90391e0cc98154a05c9815120cc98154a05", + "0x329e05528169e050b0317005528172c05830332c05528172995070bc1995", + "0x55a9e1502ae00aa502ae00aff0655c0aa502d5c0a33065580aa502d580a31", + "0x1692050e03018a502cf40a1c060314a05aa814380c062940a0c070317157", + "0x32ac0552816ac05188329e05528169e050b0332e0552816b6058303018a5", + "0x314a05060381997abd5a9e1502e5c0aa502e5c0aff0655c0aa502d5c0a33", + "0x169e050b033300552816a6058303018a502cf40a1c060314a05a4814380c", + "0x6600aa502e600aff060540aa5028540a33061780aa5029780a310653c0aa5", + "0x3018a502cf40a1c060314a059f015000c062940a0c0703330152f53c2a05", + "0x540a33061780aa5029780a310650c0aa502d0c0a16066640aa502d1c0b06", + "0x15000c062940a0c0703332152f50c2a05cc8154a05cc815fe0c0a8154a05", + "0x154a052f014620c9c0154a059c0142c0ccd0154a055d8160c0c062940b33", + "0x14180e066682a5e9c0540b9a02a940b9a02bfc181502a940a15028cc185e", + "0x1419390666c0aa502830520c062940a3102a00180c5281664058003018a5", + "0x6780aa5028305a0cce8154a05ce66c1c8f066700aa502e700a89066700aa5", + "0xc4183b02a940a3b0285819a002a940b9f02c18199f02a940b9dcf0385e0c", + "0x54bc3b0a81740055281740057f8302a05528142a0519830bc0552814bc05", + "0x1476050b03342055281510058303018a5028c40a80060314a050603819a0", + "0x6840aa502e840aff060540aa5028540a33061780aa5029780a31060ec0aa5", + "0x154a05060142c0cd10154a050e0160c0c062940a0c0703342152f0ec2a05", + "0x540ba202a940ba202bfc181502a940a15028cc185e02a940a5e028c4180c", + "0x154a0506324185e02a940a0e028391e0c070154a0506015980cd1054bc0c", + "0x2941c0502b10180502a940a0c0287418152f0380a5e02a940a5e02a141815", + "0x154a05070158c0c0b0154a052f0158a0c062940a0c070302a05d19781c0e", + "0x154a0506324180c52814180e06033480506320181c02a940a1602b1c1817", + "0x124181c02a940a0902b1c181702a940a1502b18180902a940a1d02b28181d", + "0x38182202e944805528383805538300c05528140c052f0300c05528142e05", + "0x394a0540814800c408154a0512815980c128154a0512015960c062940a0c", + "0x70188f448394a0515814800c158154a05063dc180c5281504050e0305282", + "0x145e050e031142f072940a2d02900182d02a940a2902b30180c528151205", + "0x3018a5028cc0a1c060e8660e528146205200306205528151e056603018a5", + "0xd81cf9060d80aa5028d80a89060c00aa5028e80acc060d80aa502a280acc", + "0x14180e060600ba6062941c3b02be8183b02a940a3b02a24183b02a940a30", + "0x320184002a940a88029b8188802a940a3d02ce8183d02a940a0c6483018a5", + "0x15760c428154a0506324180c5281430058003018a5028301c0c0669c0a0c", + "0x2940a4502cf4184502a940a4002cf0184002a940a43029b8184302a940a85", + "0x2940a0c0703106060701506055281506059f0300c05528140c052f0310605", + "0x180a5e061240aa50291c0b3f0611c0aa502831920c062940a2202914180c", + "0x540a1c060314a052f014e00c248181c05248154a05248167c0c030154a05", + "0x3018a5028700a5106074380e528142e05a003018a5028580a1c060314a05", + "0x900a5106088480e528140c05a00300c055281412055d83012055281418c9", + "0x2940a0c070310205d40940aa5070740b41060880aa5028880a6e060314a05", + "0x14dc0c148154a0541016780c410154a0511016840c062940a2502914180c", + "0x16840c062940a8102914180c52814180e06033520506320182b02a940a29", + "0x301c0c16817548f02a941c2b02d04182b02a940a89029b8188902a940a22", + "0x3f8188a02a940a2f02bf4182f02a940a0c7e03018a502a3c0a45060314a05", + "0x141c05198300a05528140a051883018055281418050b0306205528151405", + "0xb40a45060314a05060381831070141815028c40aa5028c40aff060380aa5", + "0x380aa5028380a33060140aa5028140a31060300aa5028300a16060314a05", + "0x301c0c0c017563b02a941c30028f418301b0e86615528141c0506178300c", + "0x1000aa502a200afe062200aa5028f40afd060f40aa5028ec0a88060314a05", + "0x15fe0c1b0154a051b014660c1d0154a051d014620c198154a05198142c0c", + "0x58188502a940a1802c18180c52814180e061006c3a198540a4002a940a40", + "0x150a057f8306c05528146c05198307405528147405188306605528146605", + "0x30bc0e072940a0502854180502a940a050297818851b0e8661502a140aa5", + "0x6b43817072941c150603a160c062940a0c070302c05d60540aa5071780a17", + "0x14bc0c0b8154a050b8142c0c048154a050e016860c062940a0c070303a05", + "0x31920c062940a0c07030120e0b9780a0902a940a0902d10180e02a940a0e", + "0x154a0507014bc0c0e8154a050e8142c0c120154a05030168a0c030154a05", + "0x154a050b0168a0c062940a0c07030480e0e9780a2402a940a2402d10180e", + "0x1780a2202a940a2202d10180e02a940a0e02978180c02a940a0c028581822", + "0x6b82c15072941c5e02b10185e02a940a0e02874180c528141947060881c0c", + "0x158e0c0e8154a050a8158c0c0e0154a050b0158a0c062940a0c070302e05", + "0x15940c030154a0506324180c52814180e060335e0506320180902a940a1c", + "0x2940a1d02924180902a940a2402b1c181d02a940a1702b18182402a940a06", + "0x314a0506038188102ec04a055283812055383044055281444052f0304405", + "0x300a16060ac0aa502a080acc060a40aa502830520c410154a0512815960c", + "0x154a05148150a0c110154a0511014bc0c028154a0502814620c060154a05", + "0xb40aa9060b51e892f2940a2b148880a0c0b520182b02a940a2b02a241829", + "0xcc620ea48306631072940a2f02ac8180c52814180e062280bb1178154a0e", + "0x154a0547814620c448154a05448142c0c1b0154a051d016940c1d0154a05", + "0x154a0545016980c062940a0c070306c8f449780a3602a940a3602d2c188f", + "0x1780a3002a940a3002d2c188f02a940a8f028c4188902a940a89028581830", + "0xec0b4d060ec0aa502831920c062940a8102914180c52814180e060c11e89", + "0x2940a0c02858188802a940a3d02d28183d02a940a181103a920c0c0154a05", + "0x1780a70062200a0c2f0151005528151005a58300a05528140a05188301805", + "0x15060c0e0154a05063dc180c528142c050e03018a5028540ab4060314a05", + "0x2940a1c02a24180602a940a0902924180c528143a0523830121d072940a17", + "0x20902252f6c84424072941c060e0380a15a70300c05528140c052f0303805", + "0x6cc0a0c6403056055281444051983052055281448051883018a5028301c0c", + "0x150205198305205528144a051883018a502a080a47060314a0506038180c", + "0xac0aa5028ac0a33060a40aa5028a40a31060300aa5028300a16060ac0aa5", + "0x301c0c18817688a02a941c2f028f4182f16a3d121552814562906178300c", + "0xd80aa5028e80afe060e80aa5028cc0afd060cc0aa502a280a88060314a05", + "0x15fe0c168154a0516814660c478154a0547814620c448154a05448142c0c", + "0x58183002a940a3102c18180c52814180e060d85a8f448540a3602a940a36", + "0x1460057f8305a05528145a05198311e05528151e05188311205528151205", + "0x540a1c060314a052f015680c062940a0e029c0183016a3d1215028c00aa5", + "0x3018a5028700a4706074380e528142c05418302e055281418f7060314a05", + "0x302b4e060240aa5028240a5e0605c0aa50285c0a89060240aa5028740a49", + "0x2080aa5028180a31060314a0506038188112888bdb5120181ca5070242e05", + "0x314a05408148e0c062940a0c0703019b602831900c148154a0512014660c", + "0xac0a89060ac0aa502831f80c148154a0512814660c410154a0511014620c", + "0x300a05028140aa502831920c062940a0c029c0182b14a08bc05158154a05", + "0x1438059d03038055281418c9060314a050b014a20c0b8581ca5028540b40", + "0x180aa5028180a6e060314a0504814a20c030241ca5028740b40060740aa5", + "0x16840c062940a2402914180c52814180e060880bb7120154a0e0b816820c", + "0x33700506320188202a940a81029b8188102a940a2502cf0182502a940a06", + "0x2940a29029b8182902a940a0602d08180c5281444052283018a5028301c0c", + "0x3018a5028ac0a45060314a0506038188902ee45605528390405a08310405", + "0x1418c8060bc0aa5028380a33060b40aa5028140a310623c0aa5028300a16", + "0x140a31060300aa5028300a16060314a05448148a0c062940a0c0703019ba", + "0xf4183a198c51415528141c0506178300c070154a0507014660c028154a05", + "0x1514050b03018a5028d80b25060314a0506038183002eec6c05528387405", + "0x3076055281418c9060bc0aa5028cc0a33060b40aa5028c40a310623c0aa5", + "0x23c2a051e8154a051e816a20c1e8154a050c016a00c0c0154a051d9781d4f", + "0x142c0c440154a0518016a60c062940a5e029c0180c52814180e060f45e2d", + "0x2940a8802d44183302a940a33028cc183102a940a31028c4188a02a940a8a", + "0x3a520c028154a0502814660c060154a0506014620c440cc628a0a8151005", + "0x4ac180c52814180e0605c0bbc0b0154a0e0a816540c0a9781c5e528140a0c", + "0x304a22120181216528143a05968303a05528143805960303805528142c05", + "0x314a0512814380c062940a2202ad0180c5281448055a03018a5028240b54", + "0x14660c070154a0507014620c410154a0540816ac0c408154a0503016aa0c", + "0x16b00c062940a0c07031045e071780a8202a940a8202d5c185e02a940a5e", + "0x2940a2902d5c185e02a940a5e028cc180e02a940a0e028c4182902a940a17", + "0x314a0506038181602ef42a5e072941c0e02830bd59060a4bc0e2f0145205", + "0x16ba0c2f0154a052f0142c0c0e0154a050b816b80c0b8154a050a816b60c", + "0x2940a0caf0303a05528141829060314a0506038181c2f0380a1c02a940a1c", + "0x30480552814182d060180aa5028243a0e478301205528141205448301205", + "0x16ba0c0b0154a050b0142c0c128154a0511016be0c110154a05030901c2f", + "0x3018a5028301c0c070177c0502a941c0c02d8018250b0380a2502a940a25", + "0x302a05028540aa5028540a3a060540aa5029780a7c061780aa5028140a7e", + "0x2940a1702a28181702a940a0e0b0385e0c0b0154a05060b4180c52814180e", + "0x301c0c070177e0502a941c0c02af4181c0281438055281438051d0303805", + "0x540aa5028540aff060540aa5029780afe061780aa5028140afd060314a05", + "0x418181702a940a0e0b0385e0c0b0154a05060b4180c52814180e060540a05", + "0x1781c5e528380a0c07584181c0281438055281438057f8303805528142e05", + "0x154a0507014620c0e8154a050a816c40c062940a0c0703038170b17b8015", + "0x14180e06033820506320182402a940a1d02d8c180602a940a5e028cc1809", + "0x300c05528142e05198301205528142c05188304405528143805b203018a5", + "0x310405e12040aa5070940b2a060940aa5028900b65060900aa5028880b63", + "0x2940a2b02af0182b02a940a2902d98182902a940a8102cac180c52814180e", + "0x151205528151205b38300c05528140c05198301205528141205188311205", + "0x301205528141205188311e05528150405b403018a5028301c0c44818125e", + "0x3018a5028328e0c47818125e02a3c0aa502a3c0b67060180aa5028180a33", + "0x3b861c0b8394a0e0b014185e1d8302c05528142c05180302c05528141836", + "0x3e8181702a940a170285818060a8394a050a815f00c062940a0c07030121d", + "0x2940a5e02da4180c528142a050e03018a5028301c0c12017880c528380c05", + "0x5c0aa50285c0a16062040aa5028940b4a060940aa5028881c0ea48304405", + "0x314a050603818810e05cbc05408154a0540816960c0e0154a050e014620c", + "0x142e0c14a081ca5028380a15060380aa5028380a5e060314a0512016000c", + "0x1419010623c0aa5028acbc0e4783018a5028301c0c448178a2b02a941c29", + "0x154a050e014620c0b8154a050b8142c0c178154a05168541cf9060b40aa5", + "0x520182f02a940a2f02a24188f02a940a8f02a14188202a940a8202978181c", + "0x14180e060d80bc61d0154a0e19815520c198c5145e528145e8f410702e16", + "0x154a050c016940c0c0154a051d8c01d49060ec600e5281474055903018a5", + "0x1780a3d02a940a3d02d2c183102a940a31028c4188a02a940a8a02858183d", + "0xc4188a02a940a8a02858188802a940a3602d30180c52814180e060f4628a", + "0x70180c52814180e06220628a2f0151005528151005a58306205528146205", + "0x2940a404103a920c200154a05448169a0c062940a5e0291c180c528142a05", + "0x303805528143805188302e05528142e050b0308605528150a05a50310a05", + "0x11c180c528142a050e03018a5028301c0c218702e5e0290c0aa50290c0b4b", + "0x31060552814187f061140aa502830520c062940a0e02a00180c52814bc05", + "0x1241c2f061240aa5028305a0c238154a05419141c8f0620c0aa502a0c0a89", + "0x2940a09028c4181d02a940a1d02858187c02a940a7e02d30187e02a940a47", + "0x301c05e38140aa5070300b6a061f0121d2f014f80552814f805a58301205", + "0x2940a1502d9c181502a940a5e02af0185e02a940a0502d98180c52814180e", + "0x5c0aa5028382c0e178302c0552814182d060314a05060381815028142a05", + "0x302a2b381bc1815198700a050e0154a050e016ce0c0e0154a050b816d00c", + "0x1c0de0c0ac14bc0e02830dc70378302a2b381bc1815061781c05061b8e06f", + "0x57925e07014186e381bc1815159c0de0c0af20bc0e02830dc70378302a2b", + "0x380a0c371c0de0c0a8ace06f06057945e07014186e381bc1815159c0de0c", + "0x381c0e0483b9a0e02830e870378302a7037830bdcc06180560e15817965e", + "0x380a0c3a1c0de0c0a93c1209219c0de0c0e73c1877158385605e70141840", + "0x7481c05061f8de0c2f0acde0c2f7440a0c3e0ac185e158301dd00b8582a5e", + "0x242c43381bc2fd30b8582a5e070141874381bc1815070242c43381bc181c", + "0x1bc18152790ce06f0605baa0c200148605ea0582a5e070141809381bcbc0e", + "0x14188a0603876290617bae0506220e06f2f1c0de0eeb054bc0e028310670", + "0x1bc1816ed8141881381bcbc703783bb40c3a0150405ec830dc0544817b00e", + "0x7741881029780bdc0a9781c05061f8de0c2f0241c2b" + ], + "sierra_program_debug_info": { + "type_names": [], + "libfunc_names": [], + "user_func_names": [] + }, + "contract_class_version": "0.1.0", + "entry_points_by_type": { + "EXTERNAL": [ + { + "selector": "0x15d40a3d6ca2ac30f4031e42be28da9b056fef9bb7357ac5e85627ee876e5ad", + "function_idx": 3 + }, + { + "selector": "0x162da33a4585851fe8d3af3c2a9c60b557814e221e0d4f30ff0b2189d9c7775", + "function_idx": 2 + }, + { + "selector": "0x1b1a0649752af1b28b3dc29a1556eee781e4a4c3a1f7f53f90fa834de098c4d", + "function_idx": 4 + }, + { + "selector": "0x289da278a8dc833409cabfdad1581e8e7d40e42dcaed693fa4008dcdb4963b3", + "function_idx": 0 + }, + { + "selector": "0x36fcbf06cd96843058359e1a75928beacfac10727dab22a3972f0af8aa92895", + "function_idx": 1 + } + ], + "L1_HANDLER": [], + "CONSTRUCTOR": [ + { + "selector": "0x28ffe4ff0f226a9107253e17a904099aa4f63a02a5621de0576e5aa71bc5194", + "function_idx": 5 + } + ] + }, + "abi": [ + { + "type": "function", + "name": "__validate_declare__", + "inputs": [ + { + "name": "class_hash", + "type": "core::felt252" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "enum", + "name": "core::bool", + "variants": [ + { + "name": "False", + "type": "()" + }, + { + "name": "True", + "type": "()" + } + ] + }, + { + "type": "function", + "name": "__validate_deploy__", + "inputs": [ + { + "name": "class_hash", + "type": "core::felt252" + }, + { + "name": "contract_address_salt", + "type": "core::felt252" + }, + { + "name": "validate_constructor", + "type": "core::bool" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "__validate__", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "function", + "name": "__execute__", + "inputs": [ + { + "name": "contract_address", + "type": "core::starknet::contract_address::ContractAddress" + }, + { + "name": "selector", + "type": "core::felt252" + }, + { + "name": "calldata", + "type": "core::array::Array::" + } + ], + "outputs": [ + { + "type": "core::felt252" + } + ], + "state_mutability": "view" + }, + { + "type": "constructor", + "name": "constructor", + "inputs": [ + { + "name": "validate_constructor", + "type": "core::bool" + } + ] + }, + { + "type": "function", + "name": "foo", + "inputs": [], + "outputs": [], + "state_mutability": "view" + }, + { + "type": "event", + "name": "account_faulty::account_faulty::Account::Event", + "kind": "enum", + "variants": [] + } + ] +} \ No newline at end of file diff --git a/crates/task_executor/Cargo.toml b/crates/task_executor/Cargo.toml new file mode 100644 index 00000000000..f622c6f95c5 --- /dev/null +++ b/crates/task_executor/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "starknet_task_executor" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +tokio.workspace = true + +[dev-dependencies] +futures.workspace = true +rstest.workspace = true +tokio-test.workspace = true + +[lints] +workspace = true diff --git a/crates/task_executor/src/executor.rs b/crates/task_executor/src/executor.rs new file mode 100644 index 00000000000..e1fb3b4ba78 --- /dev/null +++ b/crates/task_executor/src/executor.rs @@ -0,0 +1,24 @@ +use std::future::Future; + +/// An abstraction for executing tasks, suitable for both CPU-bound and I/O-bound operations. +pub trait TaskExecutor { + type SpawnBlockingError; + type SpawnError; + + /// Offloads a blocking task, _ensuring_ the async event loop remains responsive. + /// It accepts a function that executes a blocking operation and returns a result. + fn spawn_blocking( + &self, + task: F, + ) -> impl Future> + Send + where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static; + + /// Offloads a non-blocking task asynchronously. + /// It accepts a future representing an asynchronous operation and returns a result. + fn spawn(&self, task: F) -> impl Future> + Send + where + F: Future + Send + 'static, + T: Send + 'static; +} diff --git a/crates/task_executor/src/lib.rs b/crates/task_executor/src/lib.rs new file mode 100644 index 00000000000..306d004c59f --- /dev/null +++ b/crates/task_executor/src/lib.rs @@ -0,0 +1,5 @@ +//! This crate contains the async and blocking tasks Executor, which is temporarily placed here. +//! It will likely be moved to `mempool_infra` or some other infra crate in the future. + +pub mod executor; +pub mod tokio_executor; diff --git a/crates/task_executor/src/tokio_executor.rs b/crates/task_executor/src/tokio_executor.rs new file mode 100644 index 00000000000..b77c954b301 --- /dev/null +++ b/crates/task_executor/src/tokio_executor.rs @@ -0,0 +1,131 @@ +use std::future::Future; + +use tokio::runtime::Handle; + +use crate::executor::TaskExecutor; +#[cfg(test)] +#[path = "tokio_executor_test.rs"] +pub mod test; + +#[derive(Clone)] +pub struct TokioExecutor { + // Invariant: the handle must remain private to ensure all tasks spawned via this + // executor originate from the same handle, maintaining control and consistency. + handle: Handle, +} + +impl TokioExecutor { + pub fn new(handle: Handle) -> Self { + Self { handle } + } + + /// Spawns a task and returns a `JoinHandle`. + /// + /// This method is needed to allow tasks to be tracked and managed through a `JoinHandle`, + /// enabling control over task lifecycle such as awaiting completion, cancellation, or checking + /// results. It should be used only when the caller needs to manage the task directly, which is + /// essential both in testing scenarios and in the actual system `main` function. + /// Note: In most cases, where task management is not necessary, the `spawn` or + /// `spawn_blocking` methods should be preferred. + /// + /// # Example + /// ``` + /// use starknet_task_executor::tokio_executor::TokioExecutor; + /// use tokio::runtime::Handle; + /// use tokio::sync::oneshot; + /// + /// #[tokio::main] + /// async fn main() { + /// let runtime = Handle::current(); + /// let executor = TokioExecutor::new(runtime); + /// + /// // Create a oneshot channel to simulate a task waiting for a signal. + /// let (_will_not_send, await_signal_that_wont_come) = oneshot::channel::<()>(); + /// + /// // Spawn a task that waits for the signal (which we will not send). + /// let handle = executor.spawn_with_handle(async move { + /// await_signal_that_wont_come.await.ok(); + /// }); + /// + /// // Abort the task before sending the signal. + /// handle.abort(); + /// + /// assert!(handle.await.unwrap_err().is_cancelled()); + /// } + /// ``` + pub fn spawn_with_handle(&self, future: F) -> tokio::task::JoinHandle + where + F: Future + Send + 'static, + F::Output: Send + 'static, + { + self.handle.spawn(future) + } +} + +impl TaskExecutor for TokioExecutor { + /// Note: `Tokio` catches task panics that returns them as errors, this is a `Tokio`-specific + /// behavior. + type SpawnBlockingError = tokio::task::JoinError; + type SpawnError = tokio::task::JoinError; + + /// Spawns a task that may block, on a dedicated thread, preventing disruption of the async + /// runtime. + + /// # Example + /// + /// ``` + /// use starknet_task_executor::executor::TaskExecutor; + /// use starknet_task_executor::tokio_executor::TokioExecutor; + /// + /// tokio_test::block_on(async { + /// let executor = TokioExecutor::new(tokio::runtime::Handle::current()); + /// let task = || { + /// // Simulate CPU-bound work (sleep/Duration from std and not tokio!). + /// std::thread::sleep(std::time::Duration::from_millis(100)); + /// "FLOOF" + /// }; + /// let result = executor.spawn_blocking(task).await; + /// assert_eq!(result.unwrap(), "FLOOF"); + /// }); + /// ``` + fn spawn_blocking( + &self, + task: F, + ) -> impl Future> + Send + where + F: FnOnce() -> T + Send + 'static, + T: Send + 'static, + { + self.handle.spawn_blocking(task) + } + + /// Executes a async, non-blocking task. + /// + /// Note: If you need to manage the task directly through a `JoinHandle`, use + /// [`Self::spawn_with_handle`] instead. + /// + /// # Example + /// + /// ``` + /// use starknet_task_executor::{ + /// tokio_executor::TokioExecutor, executor::TaskExecutor + /// }; + /// + /// tokio_test::block_on(async { + /// let executor = TokioExecutor::new(tokio::runtime::Handle::current()); + /// let future = async { + /// // Simulate IO-bound work (sleep/Duration from tokio!). + /// tokio::time::sleep(tokio::time::Duration::from_millis(100)).await; + /// "HOPALA" + /// }; + /// let result = executor.spawn(future).await; + /// assert_eq!(result.unwrap(), "HOPALA"); + /// }); + fn spawn(&self, task: F) -> impl Future> + Send + where + F: Future + Send + 'static, + T: Send + 'static, + { + self.handle.spawn(task) + } +} diff --git a/crates/task_executor/src/tokio_executor_test.rs b/crates/task_executor/src/tokio_executor_test.rs new file mode 100644 index 00000000000..1d8bf021299 --- /dev/null +++ b/crates/task_executor/src/tokio_executor_test.rs @@ -0,0 +1,23 @@ +use rstest::{fixture, rstest}; +use tokio::runtime::Handle; + +use crate::executor::TaskExecutor; +use crate::tokio_executor::TokioExecutor; + +#[fixture] +fn executor() -> TokioExecutor { + TokioExecutor::new(Handle::current()) +} + +#[rstest] +#[tokio::test] +async fn test_panic_catching(executor: TokioExecutor) { + // Assert that panic in a task is caught and wrapped in an error. + assert!(executor.spawn_blocking(|| panic!()).await.is_err()); + // Ensure the executor remained usable after the worker thread panicked. + assert!(executor.spawn_blocking(|| "Real tasks don't panic").await.is_ok()); + + // Ditto for async tasks. + assert!(executor.spawn(async { panic!() }).await.is_err()); + assert!(executor.spawn(async { "Real async tasks don't panic" }).await.is_ok()); +} diff --git a/crates/tests-integration/Cargo.toml b/crates/tests-integration/Cargo.toml new file mode 100644 index 00000000000..df7731635aa --- /dev/null +++ b/crates/tests-integration/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "starknet_mempool_integration_tests" +version.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[lints] +workspace = true + +[dependencies] +axum.workspace = true +hyper.workspace = true +reqwest.workspace = true +# TODO(Arni, 1/5/2024): Use a fixed version once the StarkNet API is stable. +starknet_api = { git = "https://github.com/starkware-libs/starknet-api.git", branch = "main-mempool" } +starknet_gateway = { path = "../gateway", version = "0.4.0-dev.2" } + +[dev-dependencies] +mempool_infra = { path = "../mempool_infra", version = "0.4.0-dev.2" } +pretty_assertions.workspace = true +rstest.workspace = true +starknet_mempool = { path = "../mempool", version = "0.4.0-dev.2" } +starknet_mempool_types = { path = "../mempool_types", version = "0.4.0-dev.2" } +tokio.workspace = true diff --git a/crates/tests-integration/src/integration_test_utils.rs b/crates/tests-integration/src/integration_test_utils.rs new file mode 100644 index 00000000000..a76f8945d16 --- /dev/null +++ b/crates/tests-integration/src/integration_test_utils.rs @@ -0,0 +1,49 @@ +use std::net::SocketAddr; + +use axum::body::Body; +use hyper::StatusCode; +use reqwest::{Client, Response}; +use starknet_api::external_transaction::ExternalTransaction; +use starknet_api::transaction::TransactionHash; +use starknet_gateway::starknet_api_test_utils::external_tx_to_json; + +/// A test utility client for interacting with a gateway server. +pub struct GatewayClient { + socket: SocketAddr, + client: Client, +} + +impl GatewayClient { + pub fn new(socket: SocketAddr) -> Self { + let client = Client::new(); + Self { socket, client } + } + + pub async fn assert_add_tx_success(&self, tx: &ExternalTransaction) -> TransactionHash { + self.add_tx_with_status_check(tx, StatusCode::OK).await.json().await.unwrap() + } + + pub async fn add_tx_with_status_check( + &self, + tx: &ExternalTransaction, + expected_status_code: StatusCode, + ) -> Response { + let response = self.add_tx(tx).await; + assert_eq!(response.status(), expected_status_code); + + response + } + + // Prefer using assert_add_tx_success or other higher level methods of this client, to ensure + // tests are boilerplate and implementation-detail free. + pub async fn add_tx(&self, tx: &ExternalTransaction) -> Response { + let tx_json = external_tx_to_json(tx); + self.client + .post(format!("http://{}/add_tx", self.socket)) + .header("content-type", "application/json") + .body(Body::from(tx_json)) + .send() + .await + .unwrap() + } +} diff --git a/crates/tests-integration/src/lib.rs b/crates/tests-integration/src/lib.rs new file mode 100644 index 00000000000..6a3417c6012 --- /dev/null +++ b/crates/tests-integration/src/lib.rs @@ -0,0 +1 @@ +pub mod integration_test_utils; diff --git a/crates/tests-integration/tests/end_to_end_test.rs b/crates/tests-integration/tests/end_to_end_test.rs new file mode 100644 index 00000000000..862a69625ae --- /dev/null +++ b/crates/tests-integration/tests/end_to_end_test.rs @@ -0,0 +1,139 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::Arc; +use std::time::Duration; + +use mempool_infra::network_component::CommunicationInterface; +use rstest::rstest; +use starknet_api::transaction::{Tip, TransactionHash}; +use starknet_gateway::config::{ + GatewayConfig, + GatewayNetworkConfig, + StatefulTransactionValidatorConfig, + StatelessTransactionValidatorConfig, +}; +use starknet_gateway::gateway::Gateway; +use starknet_gateway::starknet_api_test_utils::invoke_tx; +use starknet_gateway::state_reader_test_utils::test_state_reader_factory; +use starknet_mempool::mempool::Mempool; +use starknet_mempool_integration_tests::integration_test_utils::GatewayClient; +use starknet_mempool_types::mempool_types::{ + BatcherToMempoolChannels, + BatcherToMempoolMessage, + GatewayNetworkComponent, + GatewayToMempoolMessage, + MempoolInput, + MempoolNetworkComponent, + MempoolToBatcherMessage, + MempoolToGatewayMessage, +}; +use tokio::sync::mpsc::channel; +use tokio::task; +use tokio::time::sleep; + +#[tokio::test] +async fn test_send_and_receive() { + let (tx_gateway_to_mempool, rx_gateway_to_mempool) = channel::(1); + let (tx_mempool_to_gateway, rx_mempool_to_gateway) = channel::(1); + + let gateway_network = + GatewayNetworkComponent::new(tx_gateway_to_mempool, rx_mempool_to_gateway); + let mut mempool_network = + MempoolNetworkComponent::new(tx_mempool_to_gateway, rx_gateway_to_mempool); + + let tx_hash = TransactionHash::default(); + let mempool_input = MempoolInput::default(); + task::spawn(async move { + let gateway_to_mempool = GatewayToMempoolMessage::AddTransaction(mempool_input); + gateway_network.send(gateway_to_mempool).await.unwrap(); + }) + .await + .unwrap(); + + let mempool_message = + task::spawn(async move { mempool_network.recv().await }).await.unwrap().unwrap(); + + match mempool_message { + GatewayToMempoolMessage::AddTransaction(mempool_input) => { + assert_eq!(mempool_input.tx.tx_hash, tx_hash); + } + } +} + +fn initialize_gateway_network_channels() -> (GatewayNetworkComponent, MempoolNetworkComponent) { + let (tx_gateway_to_mempool, rx_gateway_to_mempool) = channel::(1); + let (tx_mempool_to_gateway, rx_mempool_to_gateway) = channel::(1); + + ( + GatewayNetworkComponent::new(tx_gateway_to_mempool, rx_mempool_to_gateway), + MempoolNetworkComponent::new(tx_mempool_to_gateway, rx_gateway_to_mempool), + ) +} + +async fn set_up_gateway(network_component: GatewayNetworkComponent) -> SocketAddr { + let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); + let port = 3000; + let network_config = GatewayNetworkConfig { ip, port }; + let stateless_tx_validator_config = StatelessTransactionValidatorConfig { + validate_non_zero_l1_gas_fee: true, + max_calldata_length: 10, + max_signature_length: 2, + ..Default::default() + }; + let stateful_tx_validator_config = StatefulTransactionValidatorConfig::create_for_testing(); + let config = GatewayConfig { + network_config, + stateless_tx_validator_config, + stateful_tx_validator_config, + }; + + let state_reader_factory = Arc::new(test_state_reader_factory()); + + let gateway = Gateway::new(config, network_component, state_reader_factory); + + // Setup server + tokio::spawn(async move { gateway.run_server().await }); + + // TODO: Avoid using sleep, it slow down the test. + // Ensure the server has time to start up + sleep(Duration::from_millis(1000)).await; + SocketAddr::from((ip, port)) +} + +#[rstest] +#[tokio::test] +async fn test_end_to_end() { + let (gateway_to_mempool_network, mempool_to_gateway_network) = + initialize_gateway_network_channels(); + + let (tx_batcher_to_mempool, rx_batcher_to_mempool) = channel::(1); + let (tx_mempool_to_batcher, mut rx_mempool_to_batcher) = channel::(1); + + let batcher_channels = + BatcherToMempoolChannels { rx: rx_batcher_to_mempool, tx: tx_mempool_to_batcher }; + + // Initialize Gateway. + let socket_addr = set_up_gateway(gateway_to_mempool_network).await; + + // Send a transaction. + let external_tx = invoke_tx(); + let gateway_client = GatewayClient::new(socket_addr); + gateway_client.assert_add_tx_success(&external_tx).await; + + // Initialize Mempool. + let mut mempool = Mempool::empty(mempool_to_gateway_network, batcher_channels); + + task::spawn(async move { + mempool.run().await.unwrap(); + }); + + // TODO: Avoid using sleep, it slow down the test. + // Wait for the listener to receive the transactions. + sleep(Duration::from_secs(2)).await; + + let batcher_to_mempool_message = BatcherToMempoolMessage::GetTransactions(2); + tx_batcher_to_mempool.send(batcher_to_mempool_message).await.unwrap(); + + let mempool_message = rx_mempool_to_batcher.recv().await.unwrap(); + assert_eq!(mempool_message.len(), 1); + assert_eq!(mempool_message[0].tip, Tip(0)); +}