Source code for ethereum_rpc._rpc

"""Ethereum RPC schema."""

from dataclasses import dataclass
from enum import Enum
from typing import NewType

from ._typed_wrappers import Address, Amount, TypedData


[docs] class LogTopic(TypedData): """Log topic (32 bytes).""" def _length(self) -> int: return 32
[docs] class BlockHash(TypedData): """Block hash (32 bytes).""" def _length(self) -> int: return 32
[docs] class TxHash(TypedData): """Transaction hash (32 bytes).""" def _length(self) -> int: return 32
[docs] class TrieHash(TypedData): """Trie hash (32 bytes).""" def _length(self) -> int: return 32
[docs] class UnclesHash(TypedData): """Hash of block uncles (32 bytes).""" def _length(self) -> int: return 32
[docs] class BlockNonce(TypedData): """Block nonce (8 bytes).""" def _length(self) -> int: return 8
[docs] class LogsBloom(TypedData): """Bloom filter for logs (256 bytes).""" def _length(self) -> int: return 256
[docs] class BlockLabel(Enum): """Block label.""" LATEST = "latest" """The latest mined block.""" PENDING = "pending" """The current pending block.""" SAFE = "safe" """The latest safe head block.""" FINALIZED = "finalized" """The latest finalized block.""" EARLIEST = "earliest" """The earliest/genesis block."""
Block = int | BlockLabel """Possible values of the block parameter in RPC calls."""
[docs] @dataclass class TxInfo: """Transaction info.""" chain_id: int # TODO: make an enum? type_: int """Transaction type: 0 for legacy transactions, 2 for EIP1559 transactions.""" hash_: TxHash """Transaction hash.""" input_: None | bytes """The data sent along with the transaction.""" block_hash: None | BlockHash """The hash of the block this transaction belongs to. ``None`` for pending transactions.""" block_number: int """The number of the block this transaction belongs to. May be a pending block.""" transaction_index: None | int """Transaction index. ``None`` for pending transactions.""" from_: Address """Transaction sender.""" to: None | Address """ Transaction recipient. ``None`` when it's a contract creation transaction. """ value: Amount """Associated funds.""" nonce: int """Transaction nonce.""" gas: int """Gas used by the transaction.""" gas_price: Amount """Gas price used by the transaction.""" # TODO: we may want to have a separate derived class for EIP1559 transactions, # but for now this will do. max_fee_per_gas: None | Amount """``maxFeePerGas`` value specified by the sender. Only for EIP1559 transactions.""" max_priority_fee_per_gas: None | Amount """``maxPriorityFeePerGas`` value specified by the sender. Only for EIP1559 transactions.""" v: int """ECDSA recovery id.""" r: int """ECDSA signature r.""" s: int """ECDSA signature s."""
[docs] @dataclass class LogEntry: """Log entry metadata.""" removed: bool """ ``True`` if log was removed, due to a chain reorganization. ``False`` if it is a valid log. """ address: Address """ The contract address from which this log originated. """ data: bytes """ABI-packed non-indexed arguments of the event.""" topics: tuple[LogTopic, ...] """ Values of indexed event fields. For a named event, the first topic is the event's selector. """ # In the docs of major providers (Infura, Alchemy, Quicknode) it is claimed # that the following fields can be null if "it is a pending log". # I could not reproduce such behavior, so for now they're staying non-nullable. log_index: int """Log's position in the block.""" transaction_index: int """Transaction's position in the block.""" transaction_hash: TxHash """Hash of the transactions this log was created from.""" block_hash: BlockHash """Hash of the block where this log was in.""" block_number: int """The block number where this log was."""
[docs] @dataclass class TxReceipt: """Transaction receipt.""" block_hash: BlockHash """Hash of the block including this transaction.""" block_number: int """Block number including this transaction.""" contract_address: None | Address """ If it was a successful deployment transaction, contains the address of the deployed contract. """ cumulative_gas_used: int """The total amount of gas used when this transaction was executed in the block.""" effective_gas_price: Amount """The actual value per gas deducted from the sender's account.""" from_: Address """Address of the sender.""" gas_used: int """The amount of gas used by the transaction.""" to: None | Address """ Address of the receiver. ``None`` when the transaction is a contract creation transaction. """ transaction_hash: TxHash """Hash of the transaction.""" transaction_index: int """Integer of the transaction's index position in the block.""" # TODO: make an enum? type_: int """Transaction type: 0 for legacy transactions, 2 for EIP1559 transactions.""" status: int """1 if the transaction was successful, 0 otherwise.""" logs: tuple[LogEntry, ...] """An array of log objects generated by this transaction.""" logs_bloom: LogsBloom """Bloom filter for light clients to quickly retrieve related logs.""" @property def succeeded(self) -> bool: """``True`` if the transaction succeeded.""" return self.status == 1
[docs] @dataclass class BlockInfo: """Block info.""" number: int """Block number.""" hash_: None | BlockHash """Block hash. ``None`` for pending blocks.""" parent_hash: BlockHash """Parent block's hash.""" nonce: None | BlockNonce """Block's nonce. ``None`` for pending blocks.""" miner: None | Address """Block's miner. ``None`` for pending blocks.""" difficulty: int """Block's difficulty.""" total_difficulty: None | int """Block's totat difficulty. ``None`` for pending blocks.""" size: int """Block size.""" gas_limit: int """Block's gas limit.""" gas_used: int """Gas used for the block.""" base_fee_per_gas: Amount """Base fee per gas in this block.""" timestamp: int """Block's timestamp.""" transactions: tuple[TxInfo, ...] | tuple[TxHash, ...] """ A list of transaction hashes in this block, or a list of details of transactions in this block, depending on what was requested. """ uncles: tuple[BlockHash, ...] """Array of uncle hashes.""" sha3_uncles: UnclesHash """SHA3 of the uncles data in the block.""" logs_bloom: None | LogsBloom """The bloom filter for the logs of the block. ``None`` for pending blocks.""" transactions_root: TrieHash """The root of the transaction trie of the block.""" state_root: TrieHash """The root of the final state trie of the block.""" receipts_root: TrieHash """The root of the receipts trie of the block.""" extra_data: bytes """The "extra data" field of this block."""
[docs] class RPCErrorCode(Enum): """Standard RPC error codes returned by providers.""" PARSE_ERROR = -32700 """ Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text. """ SERVER_ERROR = -32000 """Reserved for implementation-defined server-errors. See the message for details.""" INVALID_REQUEST = -32600 """The JSON sent is not a valid Request object.""" METHOD_NOT_FOUND = -32601 """The method does not exist / is not available.""" INVALID_PARAMETER = -32602 """Invalid method parameter(s).""" INTERNAL_ERROR = -32603 """Internal JSON-RPC error.""" EXECUTION_ERROR = 3 """Contract transaction failed during execution. See the data for details."""
# Need a newtype because unlike all other integers, this one is not hexified on serialization. ErrorCode = NewType("ErrorCode", int)
[docs] @dataclass class RPCError(Exception): """ A general problem with fulfilling the request at the provider's side. This means the provider sent a correct response with an error code and possibly some associated data (``"error": {"code": ..., "message": ..., "data": ...}`` sub-dictionary in the RPC response). """ code: ErrorCode """The error type.""" message: str """The associated message.""" data: None | bytes = None """The associated data (if any).""" @property def parsed_code(self) -> None | RPCErrorCode: """If the error code is known, returns the corresponding enum entry.""" try: return RPCErrorCode(self.code) except ValueError: return None def __str__(self) -> str: # Substitute the known code if any, or report the raw integer value otherwise code = self.parsed_code or self.code return f"RPC error ({code}): {self.message}" + ( f" (data: {self.data.hex()})" if self.data else "" )
[docs] @classmethod def with_code(cls, code: RPCErrorCode, message: str, data: None | bytes = None) -> "RPCError": """Creates this error given a known error code.""" return cls(ErrorCode(code.value), message, data=data)
[docs] @dataclass class Type2Transaction: """An EIP-1559 (dynamic fee) transaction.""" # "type": 2 chain_id: int """Chain ID.""" value: Amount """Associated funds.""" gas: int """Gas limit for the transaction.""" max_fee_per_gas: Amount """Maximum total fee the sender is willing to pay.""" max_priority_fee_per_gas: Amount """Maximum miner fee (in addition to the base fee) the sender is willing to pay.""" nonce: int """The transaction nonce.""" to: None | Address = None """The destination of the transaction. ``None`` if it's a deployment transaction.""" data: None | bytes = None """The associated data of the transaction."""
[docs] @dataclass class EthCallParams: """Transaction fields for ``eth_call``.""" to: Address """The transaction destination (the contract address).""" from_: None | Address = None """ The transaction sender. May matter for some contract methods that depend on ``msg.sender``. """ gas: None | int = None """ Gas provided for the transaction execution. ``eth_call`` consumes zero gas, but this parameter may be needed by some executions. """ gas_price: None | Amount = None """The gas price.""" value: None | Amount = None """The associated funds.""" data: None | bytes = None """The associated data of the transaction."""
[docs] @dataclass class EstimateGasParams: """Transaction fields for ``eth_estimateGas``.""" from_: Address """The transaction sender.""" to: None | Address = None """The destination of the transaction. ``None`` if it's a deployment transaction.""" gas: None | int = None """Gas provided for the transaction execution.""" gas_price: None | Amount = None """The gas price.""" nonce: None | int = None """The transaction nonce.""" value: None | Amount = None """The associated funds.""" data: None | bytes = None """The associated data of the transaction."""
[docs] @dataclass class FilterParams: """Filter parameters for ``eth_getLogs`` or ``eth_newFilter``.""" from_block: None | Block = None """The starting block of the filter.""" to_block: None | Block = None """The ending block of the filter (inclusive).""" address: None | Address | tuple[Address, ...] = None """Filter by one or several source addresses.""" topics: None | tuple[None | LogTopic | tuple[LogTopic, ...], ...] = None """Log topics."""
[docs] @dataclass class FilterParamsEIP234: """Alternative filter parameters for ``eth_getLogs`` (introduced in EIP-234).""" block_hash: BlockHash """The hash of the block to which the filter is applied.""" address: None | Address | tuple[Address, ...] = None """Filter by one or several source addresses.""" topics: None | tuple[None | LogTopic | tuple[LogTopic, ...], ...] = None """Log topics."""