diff --git a/starknet_py/constants.py b/starknet_py/constants.py index e6656606c..f398a5ddd 100644 --- a/starknet_py/constants.py +++ b/starknet_py/constants.py @@ -53,7 +53,7 @@ class OutsideExecutionInterfaceID(IntEnum): V2 = 0x1D1144BB2138366FF28D8E9AB57456B1D332AC42196230C3A602003C89872 -EXPECTED_RPC_VERSION = "0.10.0" +EXPECTED_RPC_VERSION = "0.10.1-rc.2" ARGENT_V040_CLASS_HASH = ( 0x036078334509B514626504EDC9FB252328D1A240E4E948BEF8D0C08DFF45927F diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 2802f35e0..2c1d65c18 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -390,6 +390,8 @@ async def invoke( nonce: Optional[int] = None, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> InvokeResult: """ Send an Invoke transaction version 3 for the prepared data. @@ -399,6 +401,8 @@ async def invoke( :param nonce: Nonce of the transaction. :param tip: The tip amount to be added to the transaction fee. :param auto_estimate_tip: Use automatic tip estimation. Using this option may lead to higher costs. + :param proof_facts: Optional proof facts for the transaction. + :param proof: Optional proof for the transaction. :return: InvokeResult. """ @@ -409,6 +413,8 @@ async def invoke( auto_estimate=auto_estimate, tip=tip or self.tip, auto_estimate_tip=auto_estimate_tip, + proof_facts=proof_facts, + proof=proof, ) return await self._invoke(transaction) @@ -563,6 +569,8 @@ async def invoke_v3( tip: Optional[int] = None, auto_estimate_tip: bool = False, nonce: Optional[int] = None, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, **kwargs, ) -> InvokeResult: """ @@ -574,6 +582,8 @@ async def invoke_v3( :param tip: The tip amount to be added to the transaction fee. :param auto_estimate_tip: Use automatic tip estimation. Using this option may lead to higher costs. :param nonce: Nonce of the transaction. + :param proof_facts: Optional proof facts for the transaction. + :param proof: Optional proof for the transaction. :return: InvokeResult. """ prepared_invoke = self.prepare_invoke_v3(*args, **kwargs) @@ -583,6 +593,8 @@ async def invoke_v3( auto_estimate=auto_estimate, tip=tip, auto_estimate_tip=auto_estimate_tip, + proof_facts=proof_facts, + proof=proof, ) @staticmethod diff --git a/starknet_py/hash/transaction.py b/starknet_py/hash/transaction.py index fb4d4fb44..f8f78e1a0 100644 --- a/starknet_py/hash/transaction.py +++ b/starknet_py/hash/transaction.py @@ -182,6 +182,7 @@ def compute_invoke_v3_transaction_hash( account_deployment_data: List[int], calldata: List[int], common_fields: CommonTransactionV3Fields, + proof_facts: Optional[List[int]] = None, ) -> int: """ Computes hash of an Invoke transaction version 3. @@ -190,15 +191,18 @@ def compute_invoke_v3_transaction_hash( Currently, this value is always empty. :param calldata: Calldata of the function. :param common_fields: Common fields for V3 transactions. + :param proof_facts: Optional proof facts for the transaction. :return: Hash of the transaction. """ - return poseidon_hash_many( - [ - *common_fields.compute_common_tx_fields(), - poseidon_hash_many(account_deployment_data), - poseidon_hash_many(calldata), - ] - ) + elements = [ + *common_fields.compute_common_tx_fields(), + poseidon_hash_many(account_deployment_data), + poseidon_hash_many(calldata), + ] + if proof_facts: + elements.append(poseidon_hash_many(proof_facts)) + + return poseidon_hash_many(elements) def compute_deploy_account_transaction_hash( diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index 0f6ac0ec3..3baa49dc8 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -185,6 +185,8 @@ async def _prepare_invoke_v3( auto_estimate: bool = False, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> InvokeV3: # pylint: disable=too-many-arguments """ @@ -210,6 +212,8 @@ async def _prepare_invoke_v3( sender_address=self.address, version=3, tip=tip, + proof_facts=proof_facts or [], + proof=proof or [], ) resource_bounds = await self._get_resource_bounds( @@ -399,6 +403,8 @@ async def sign_invoke_v3( auto_estimate: bool = False, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> InvokeV3: # pylint: disable=too-many-arguments invoke_tx = await self._prepare_invoke_v3( @@ -408,6 +414,8 @@ async def sign_invoke_v3( auto_estimate=auto_estimate, tip=tip, auto_estimate_tip=auto_estimate_tip, + proof_facts=proof_facts, + proof=proof, ) signature = self.signer.sign_transaction(invoke_tx) return _add_signature_to_transaction(invoke_tx, signature) @@ -513,6 +521,8 @@ async def execute_v3( auto_estimate: bool = False, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> SentTransactionResponse: # pylint: disable=too-many-arguments execute_transaction = await self.sign_invoke_v3( @@ -522,6 +532,8 @@ async def execute_v3( auto_estimate=auto_estimate, tip=tip, auto_estimate_tip=auto_estimate_tip, + proof_facts=proof_facts, + proof=proof, ) return await self._client.send_transaction(execute_transaction) diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index db33160ed..8530bf49a 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -167,6 +167,8 @@ async def sign_invoke_v3( auto_estimate: bool = False, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> InvokeV3: # pylint: disable=too-many-arguments """ @@ -178,6 +180,8 @@ async def sign_invoke_v3( :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :param tip: The tip amount to be added to the transaction fee. :param auto_estimate_tip: Use automatic tip estimation. Using this option may lead to higher costs. + :param proof_facts: Optional proof facts for the transaction. + :param proof: Optional proof for the transaction. :return: Invoke created from the calls. """ @@ -249,6 +253,8 @@ async def execute_v3( auto_estimate: bool = False, tip: Optional[int] = None, auto_estimate_tip: bool = False, + proof_facts: Optional[List[int]] = None, + proof: Optional[List[int]] = None, ) -> SentTransactionResponse: # pylint: disable=too-many-arguments """ @@ -260,6 +266,8 @@ async def execute_v3( :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. :param tip: The tip amount to be added to the transaction fee. :param auto_estimate_tip: Use automatic tip estimation. Using this option may lead to higher costs. + :param proof_facts: Optional proof facts for the transaction. + :param proof: Optional proof for the transaction. :return: SentTransactionResponse. """ diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index c9a076c3a..51f10814c 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -28,9 +28,11 @@ StarknetBlockWithTxHashes, StorageProofResponse, Tag, + TraceFlag, Transaction, TransactionExecutionStatus, TransactionReceiptWithBlockInfo, + TransactionResponseFlag, TransactionStatus, TransactionStatusResponse, ) @@ -72,12 +74,14 @@ async def get_block_with_txs( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: """ Retrieve the block's data by its number or hash. :param block_hash: Block's hash or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` :param block_number: Block's number or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` + :param response_flags: Flags that control what additional fields are included in transaction responses :return: StarknetBlock object representing retrieved block with transactions. """ @@ -86,12 +90,14 @@ async def get_block_with_tx_hashes( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlockWithTxHashes, PreConfirmedStarknetBlockWithTxHashes]: """ Retrieve the block's data with a list of contained transaction hashes. :param block_hash: Block's hash or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` :param block_number: Block's number or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` + :param response_flags: Flags that control what additional fields are included in transaction responses :return: StarknetBlockWithTxHashes object representing retrieved block with transactions. """ @@ -100,12 +106,14 @@ async def get_block_with_receipts( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlockWithReceipts, PreConfirmedStarknetBlockWithReceipts]: """ Retrieve the block's data with a list of receipts for contained transactions. :param block_hash: Block's hash or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` :param block_number: Block's number or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` + :param response_flags: Flags that control what additional fields are included in transaction responses :return: StarknetBlockWithReceipts object representing retrieved block with transactions. """ @@ -114,12 +122,14 @@ async def trace_block_transactions( self, block_hash: Optional[Union[Hash, LatestTag]] = None, block_number: Optional[Union[int, LatestTag]] = None, + trace_flags: Optional[List[TraceFlag]] = None, ) -> List[BlockTransactionTrace]: """ Receive the traces of all the transactions within specified block :param block_hash: Block's hash :param block_number: Block's number or "pre_confirmed" for pre_confirmed block + :param trace_flags: Flags that indicate when additional information should be included in the trace :return: BlockTransactionTraces object representing received traces """ @@ -178,6 +188,7 @@ async def get_storage_proof( async def get_transaction( self, tx_hash: Hash, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Transaction: """ Get the details and status of a submitted transaction diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index f15d6413e..c1a1d992d 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -292,6 +292,8 @@ class InvokeTransactionV3(TransactionV3): sender_address: int nonce: int account_deployment_data: List[int] + proof: Optional[List[int]] = None + proof_facts: Optional[List[int]] = None @dataclass @@ -638,6 +640,10 @@ class StarknetBlockWithTxHashes(BlockHeader): transactions: List[int] +class TransactionResponseFlag(str, Enum): + INCLUDE_PROOF_FACTS = "INCLUDE_PROOF_FACTS" + + @dataclass class StarknetBlockWithReceipts(BlockHeader): """ @@ -1052,6 +1058,53 @@ class SimulationFlag(str, Enum): SKIP_VALIDATE = "SKIP_VALIDATE" SKIP_FEE_CHARGE = "SKIP_FEE_CHARGE" + RETURN_INITIAL_READS = "RETURN_INITIAL_READS" + + +class TraceFlag(str, Enum): + """ + Enum class representing flags that indicate what additional information should be included in the trace. + """ + + RETURN_INITIAL_READS = "RETURN_INITIAL_READS" + + +@dataclass +class StorageInitialRead: + contract_address: int + key: str + value: int + + +@dataclass +class NonceInitialRead: + contract_address: int + nonce: int + + +@dataclass +class ClassHashInitialRead: + contract_address: int + class_hash: int + + +@dataclass +class DeclaredContractInitialRead: + class_hash: int + is_declared: bool + + +@dataclass +class InitialReads: + """ + Dataclass representing the set of state values fetched from + the underlying state reader during execution. + """ + + storage: Optional[List[StorageInitialRead]] = None + nonces: Optional[List[NonceInitialRead]] = None + class_hashes: Optional[List[ClassHashInitialRead]] = None + declared_contracts: Optional[List[DeclaredContractInitialRead]] = None class EntryPointType(Enum): @@ -1172,6 +1225,18 @@ class SimulatedTransaction: fee_estimation: EstimatedFee +@dataclass +class SimulatedTransactionsWithInitialReads: + """ + Dataclass representing the execution trace and consumed resources + of the required transactions, along with initial reads when + RETURN_INITIAL_READS is present in simulation_flags. + """ + + simulated_transactions: List[SimulatedTransaction] + initial_reads: InitialReads + + @dataclass class BlockTransactionTrace: """ @@ -1182,6 +1247,17 @@ class BlockTransactionTrace: trace_root: TransactionTrace +@dataclass +class BlockTransactionTracesWithInitialReads: + """ + Dataclass representing the traces of all transactions in the block, along with the initial reads + when RETURN_INITIAL_READS is present in trace_flags. + """ + + transaction_traces: List[BlockTransactionTrace] + initial_reads: InitialReads + + @dataclass class BinaryNode: """ diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index 952c131e2..c9963299d 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -10,6 +10,7 @@ BlockHashAndNumber, BlockStateUpdate, BlockTransactionTrace, + BlockTransactionTracesWithInitialReads, Call, ContractsStorageKeys, DeclareTransactionResponse, @@ -28,6 +29,7 @@ SentTransactionResponse, SierraContractClass, SimulatedTransaction, + SimulatedTransactionsWithInitialReads, SimulationFlag, StarknetBlock, StarknetBlockWithReceipts, @@ -35,8 +37,10 @@ StorageProofResponse, SyncStatus, Tag, + TraceFlag, Transaction, TransactionReceiptWithBlockInfo, + TransactionResponseFlag, TransactionStatusResponse, TransactionTrace, ) @@ -138,21 +142,44 @@ async def get_block_with_txs( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: - return await self.get_block(block_hash=block_hash, block_number=block_number) + block_identifier = get_block_identifier( + block_hash=block_hash, block_number=block_number + ) + + params = {**block_identifier} + if response_flags is not None: + params["response_flags"] = response_flags + + res = await self._client.call( + method_name="getBlockWithTxs", + params=params, + ) + + if block_identifier == {"block_id": "pre_confirmed"}: + return cast( + PreConfirmedStarknetBlock, PreConfirmedStarknetBlockSchema().load(res) + ) + return cast(StarknetBlock, StarknetBlockSchema().load(res)) async def get_block_with_tx_hashes( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlockWithTxHashes, PreConfirmedStarknetBlockWithTxHashes]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) + params = {**block_identifier} + if response_flags is not None: + params["response_flags"] = response_flags + res = await self._client.call( method_name="getBlockWithTxHashes", - params=block_identifier, + params=params, ) if block_identifier == {"block_id": "pre_confirmed"}: @@ -169,6 +196,7 @@ async def get_block_with_receipts( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Union[StarknetBlockWithReceipts, PreConfirmedStarknetBlockWithReceipts]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number @@ -192,7 +220,7 @@ async def get_block_with_receipts( # TODO (#809): add tests with multiple emitted keys async def get_events( self, - address: Optional[Hash] = None, + address: Optional[Union[Hash, List[Hash]]] = None, keys: Optional[List[List[Hash]]] = None, *, from_block_number: Optional[Union[int, Tag]] = None, @@ -205,7 +233,7 @@ async def get_events( ) -> EventsChunk: # pylint: disable=too-many-arguments """ - :param address: The address of the contract that emitted the event. + :param address: A contract address or a list of addresses from which events should originate. :param keys: List consisting lists of keys by which the events are filtered. They match the keys *by position*, e.g. given an event with 3 keys, [[1,2],[],[3]] which should return events that have either 1 or 2 in the first key, any value for their second key and 3 for their third key. @@ -239,7 +267,10 @@ async def get_events( if keys is None: keys = [] if address is not None: - address = _to_rpc_felt(address) + if isinstance(address, list): + address = [_to_rpc_felt(addr) for addr in address] + else: + address = _to_rpc_felt(address) if from_block_number is None and from_block_hash is None: from_block_number = 0 @@ -276,7 +307,7 @@ async def _get_events_chunk( to_block: Union[dict, Hash, Tag, None], keys: List[List[Hash]], chunk_size: int, - address: Optional[Hash] = None, + address: Optional[Union[Hash, List[Hash]]] = None, continuation_token: Optional[str] = None, ) -> Tuple[list, Optional[str]]: # pylint: disable=too-many-arguments @@ -400,11 +431,17 @@ async def get_storage_proof( async def get_transaction( self, tx_hash: Hash, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Transaction: try: + params = {"transaction_hash": _to_rpc_felt(tx_hash)} + + if response_flags is not None: + params["response_flags"] = response_flags + res = await self._client.call( method_name="getTransactionByHash", - params={"transaction_hash": _to_rpc_felt(tx_hash)}, + params=params, ) except ClientError as ex: raise TransactionNotReceivedError() from ex @@ -662,6 +699,7 @@ async def get_transaction_by_block_id( index: int, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, + response_flags: Optional[List[TransactionResponseFlag]] = None, ) -> Transaction: """ Get the details of transaction in block identified by block_hash and transaction index. @@ -669,18 +707,23 @@ async def get_transaction_by_block_id( :param index: Index of the transaction :param block_hash: Hash of the block :param block_number: Block's number or literals `"l1_accepted"`, `"pre_confirmed"` or `"latest"` + :param response_flags: Flags that control what additional fields are included in transaction responses :return: Transaction object """ block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) + params = { + **block_identifier, + "index": index, + } + if response_flags is not None: + params["response_flags"] = response_flags + res = await self._client.call( method_name="getTransactionByBlockIdAndIndex", - params={ - **block_identifier, - "index": index, - }, + params=params, ) return cast(Transaction, TypesOfTransactionsSchema().load(res)) @@ -818,7 +861,10 @@ async def simulate_transactions( skip_fee_charge: bool = False, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> List[SimulatedTransaction]: + ) -> Union[ + List[SimulatedTransaction], + SimulatedTransactionsWithInitialReads, + ]: # pylint: disable=too-many-arguments """ Simulates a given sequence of transactions on the requested state, and generates the execution traces. @@ -868,17 +914,25 @@ async def trace_block_transactions( self, block_hash: Optional[Union[Hash, LatestTag]] = None, block_number: Optional[Union[int, LatestTag]] = None, - ) -> List[BlockTransactionTrace]: + trace_flags: Optional[List[TraceFlag]] = None, + ) -> Union[List[BlockTransactionTrace], BlockTransactionTracesWithInitialReads]: """ Retrieve traces for all transactions in the given block. :param block_hash: Block's hash or literals `"pre_confirmed"`. :param block_number: Block's number or literals `"pre_confirmed"`. + :param trace_flags: Flags that indicate when additional information should be included in the trace :return: List of execution traces of all transactions included in the given block with transaction hashes. """ block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number, allow_pre_confirmed=False ) + params = { + **block_identifier, + } + + if trace_flags is not None: + params["trace_flags"] = trace_flags res = await self._client.call( method_name="traceBlockTransactions", diff --git a/starknet_py/net/models/transaction.py b/starknet_py/net/models/transaction.py index 3f0c10e7f..35046a7ed 100644 --- a/starknet_py/net/models/transaction.py +++ b/starknet_py/net/models/transaction.py @@ -326,6 +326,8 @@ class InvokeV3(_AccountTransactionV3): calldata: List[int] sender_address: int account_deployment_data: List[int] = field(default_factory=list) + proof: List[int] = None + proof_facts: List[int] = None @property def type(self) -> TransactionType: diff --git a/starknet_py/net/schemas/rpc/trace_api.py b/starknet_py/net/schemas/rpc/trace_api.py index 7264608f2..16387b21b 100644 --- a/starknet_py/net/schemas/rpc/trace_api.py +++ b/starknet_py/net/schemas/rpc/trace_api.py @@ -3,15 +3,21 @@ from starknet_py.net.client_models import ( BlockTransactionTrace, + ClassHashInitialRead, + DeclaredContractInitialRead, DeclareTransactionTrace, DeployAccountTransactionTrace, FunctionInvocation, + InitialReads, InvokeTransactionTrace, L1HandlerTransactionTrace, + NonceInitialRead, OrderedEvent, OrderedMessage, RevertedFunctionInvocation, SimulatedTransaction, + SimulatedTransactionsWithInitialReads, + StorageInitialRead, ) from starknet_py.net.schemas.common import CallTypeField, EntryPointTypeField, Felt from starknet_py.net.schemas.rpc.block import StateDiffSchema @@ -213,6 +219,21 @@ def make_dataclass(self, data, **kwargs) -> SimulatedTransaction: return SimulatedTransaction(**data) +class SimulatedTransactionsWithInitialReadsSchema(Schema): + simulated_transactions = fields.List( + fields.Nested(SimulatedTransactionSchema()), + data_key="simulated_transactions", + required=True, + ) + initial_reads = fields.Dict( + keys=Felt(), values=Felt(), data_key="initial_reads", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> SimulatedTransactionsWithInitialReads: + return SimulatedTransactionsWithInitialReads(**data) + + class BlockTransactionTraceSchema(Schema): transaction_hash = Felt(data_key="transaction_hash", required=True) # `unknown=EXCLUDE` in order to skip `type=...` field we don't want @@ -223,3 +244,82 @@ class BlockTransactionTraceSchema(Schema): @post_load def make_dataclass(self, data, **kwargs) -> BlockTransactionTrace: return BlockTransactionTrace(**data) + + +class StorageInitialReadSchema(Schema): + contract_address = Felt(data_key="contract_address", required=True) + key = fields.String(data_key="key", required=True) + value = Felt(data_key="value", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> StorageInitialRead: + return StorageInitialRead(**data) + + +class NonceInitialReadSchema(Schema): + contract_address = Felt(data_key="contract_address", required=True) + nonce = Felt(data_key="nonce", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> NonceInitialRead: + return NonceInitialRead(**data) + + +class ClassHashInitialReadSchema(Schema): + contract_address = Felt(data_key="contract_address", required=True) + class_hash = Felt(data_key="class_hash", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> ClassHashInitialRead: + return ClassHashInitialRead(**data) + + +class DeclaredContractInitialReadSchema(Schema): + class_hash = Felt(data_key="class_hash", required=True) + is_declared = fields.Boolean(data_key="is_declared", required=True) + + @post_load + def make_dataclass(self, data, **kwargs) -> DeclaredContractInitialRead: + return DeclaredContractInitialRead(**data) + + +class InitialReadsSchema(Schema): + storage = fields.List( + fields.Nested(StorageInitialReadSchema()), + data_key="storage", + load_default=None, + ) + nonces = fields.List( + fields.Nested(NonceInitialReadSchema()), + data_key="nonces", + load_default=None, + ) + class_hashes = fields.List( + fields.Nested(ClassHashInitialReadSchema()), + data_key="class_hashes", + load_default=None, + ) + declared_contracts = fields.List( + fields.Nested(DeclaredContractInitialReadSchema()), + data_key="declared_contracts", + load_default=None, + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> InitialReads: + return InitialReads(**data) + + +class BlockTransactionTracesSchema(Schema): + transaction_traces = fields.List( + fields.Nested(BlockTransactionTraceSchema()), + data_key="transaction_traces", + required=True, + ) + initial_reads = fields.Dict( + keys=Felt(), values=Felt(), data_key="initial_reads", required=True + ) + + @post_load + def make_dataclass(self, data, **kwargs) -> BlockTransactionTrace: + return BlockTransactionTrace(**data) diff --git a/starknet_py/net/schemas/rpc/transactions.py b/starknet_py/net/schemas/rpc/transactions.py index 9ec161cf8..b43354a58 100644 --- a/starknet_py/net/schemas/rpc/transactions.py +++ b/starknet_py/net/schemas/rpc/transactions.py @@ -182,6 +182,8 @@ class InvokeTransactionV3Schema(TransactionV3Schema): account_deployment_data = fields.List( Felt(), data_key="account_deployment_data", required=True ) + proof = fields.List(fields.Integer(), data_key="proof", required=False) + proof_facts = fields.List(Felt(), data_key="proof_facts", required=False) @post_load def make_transaction(self, data, **kwargs) -> InvokeTransactionV3: diff --git a/starknet_py/net/websockets/models.py b/starknet_py/net/websockets/models.py index 28508f5a2..1643f0e18 100644 --- a/starknet_py/net/websockets/models.py +++ b/starknet_py/net/websockets/models.py @@ -3,6 +3,7 @@ """ from dataclasses import dataclass +from enum import Enum from typing import Generic, TypeVar from starknet_py.net.client_models import ( @@ -95,3 +96,12 @@ class NewTransactionNotification(Notification[NewTransactionNotificationResult]) """ Notification of a new transaction """ + + +class SubscriptionTag(str, Enum): + """ + Enum representing tags that control what additional fields + are included in subscription responses. + """ + + INCLUDE_PROOF_FACTS = "INCLUDE_PROOF_FACTS" diff --git a/starknet_py/net/websockets/websocket_client.py b/starknet_py/net/websockets/websocket_client.py index 5574725d9..5d189c229 100644 --- a/starknet_py/net/websockets/websocket_client.py +++ b/starknet_py/net/websockets/websocket_client.py @@ -27,6 +27,7 @@ NewTransactionNotification, NewTransactionReceiptsNotification, ReorgNotification, + SubscriptionTag, TransactionStatusNotification, ) @@ -149,7 +150,7 @@ async def subscribe_new_heads( async def subscribe_events( self, handler: Callable[[NewEventsNotification], Any], - from_address: Optional[int] = None, + from_address: Optional[Union[List[int], int]] = None, keys: Optional[List[List[int]]] = None, block_hash: Optional[Union[Hash, LatestTag]] = None, block_number: Optional[Union[int, LatestTag]] = None, @@ -159,7 +160,7 @@ async def subscribe_events( Creates a WebSocket stream which will fire events for new Starknet events with applied filters. :param handler: The function to call when a new event is received. - :param from_address: Address which emitted the event. + :param from_address: A contract address or a list of addresses from which events should originate. :param keys: The keys to filter events by. :param block_hash: Hash of the block to get notifications from or literal `"latest"`. Mutually exclusive with ``block_number`` parameter. If not provided, queries block `"latest"`. @@ -216,6 +217,7 @@ async def subscribe_new_transactions( handler: Callable[[NewTransactionNotification], Any], sender_address: Optional[List[int]] = None, finality_status: Optional[List[TransactionStatusWithoutL1]] = None, + tags: Optional[List[SubscriptionTag]] = None, ) -> str: """ Creates a WebSocket stream which will fire events when a new pending transaction is added. @@ -224,6 +226,7 @@ async def subscribe_new_transactions( :param handler: The function to call when a new pending transaction is received. :param sender_address: List of sender addresses to filter transactions by. :param finality_status: The finality statuses to filter transaction receipts by, default is [ACCEPTED_ON_L2]. + :param tags: Tags that control what additional fields are included in transaction responses. :return: The subscription ID. """ @@ -235,6 +238,9 @@ async def subscribe_new_transactions( if finality_status is not None: params["finality_status"] = [status.value for status in finality_status] + if tags is not None: + params["tags"] = tags + subscription_id = await self._subscribe( handler, "starknet_subscribeNewTransactions", params ) diff --git a/starknet_py/tests/unit/hash/transaction_test.py b/starknet_py/tests/unit/hash/transaction_test.py index 19a817eed..d2f9002da 100644 --- a/starknet_py/tests/unit/hash/transaction_test.py +++ b/starknet_py/tests/unit/hash/transaction_test.py @@ -12,6 +12,7 @@ compute_transaction_hash, ) from starknet_py.net.client_models import DAMode +from starknet_py.net.models import StarknetChainId from starknet_py.tests.e2e.fixtures.constants import MAX_RESOURCE_BOUNDS @@ -173,9 +174,52 @@ def test_compute_declare_v3_transaction_hash(common_data, declare_data, expected }, 0x119386B4AAAEF905BF027D3DD2734474C5E944942BF3FBD8FDB442704D32B8B, ), + # https://github.com/starkware-libs/sequencer/blob/69b4c8d378580a1aec345f23dadd4766e036fe3b/crates/starknet_api/resources/transaction_hash.json#L116 + ( + { + "address": 0x69C0F9BCD79697BDCEAF7748E3FF8F34AA39E4063CE44896AF664C0C96F6C10, + "chain_id": 0x534E5F4D41494E, + "nonce": 0x9D, + "tip": 0x0, + "paymaster_data": [], + "nonce_data_availability_mode": DAMode.L1, + "fee_data_availability_mode": DAMode.L1, + "tx_prefix": TransactionHashPrefix.INVOKE, + "version": 0x3, + }, + { + "calldata": [ + int("0x1", 16), + int( + "0x4c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05", + 16, + ), + int( + "0x3943907ef0ef6f9d2e2408b05e520a66daaf74293dbf665e5a20b117676170e", + 16, + ), + int("0x2", 16), + int( + "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", + 16, + ), + int("0x16345785d8a0000", 16), + ], + "account_deployment_data": [], + "proof_facts": [ + int("0x1", 16), + int("0x2", 16), + int("0x3", 16), + ], + }, + 0x6D885B1A2B7CB7946480C63AA1697888A33E9CCD0B1516F41C41731A1628726, + ), ), ) def test_compute_invoke_v3_transaction_hash(common_data, invoke_data, expected_hash): + print("integration", hex(StarknetChainId.SEPOLIA_INTEGRATION)) + print("mainnet", hex(StarknetChainId.MAINNET)) + print("testnet", hex(StarknetChainId.SEPOLIA)) assert ( compute_invoke_v3_transaction_hash( **invoke_data,