Skip to content

Commit

Permalink
Add support for safe and finalized block identifiers
Browse files Browse the repository at this point in the history
- ``safe`` and ``finalized`` is sometimes accepted and sometimes not when it comes to filters. They are not yet part of the specifications so the documentation should reflect this. However, when creating event filters for example, these are supported block ids in Infura and they do yield different results than, say, "latest". Because of this, we should support it but provide careful documentation that they may or may not yield desirable results.
  • Loading branch information
fselmo committed Sep 21, 2022
1 parent 541c768 commit f3b8bf4
Show file tree
Hide file tree
Showing 11 changed files with 76 additions and 33 deletions.
22 changes: 17 additions & 5 deletions docs/filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Filtering
from web3.auto import w3
The :meth:`web3.eth.Eth.filter` method can be used to setup filters for:
The :meth:`web3.eth.Eth.filter` method can be used to set up filters for:

* Pending Transactions: ``web3.eth.filter('pending')``

Expand Down Expand Up @@ -44,8 +44,9 @@ The :meth:`web3.eth.Eth.filter` method can be used to setup filters for:
.. note ::
Creating event filters requires that your Ethereum node has an API support enabled for filters.
It does not work with Infura nodes. To get event logs on Infura or other
stateless nodes please see :class:`web3.contract.ContractEvents`.
Note that Infura support for filters does not offer access to `pending` filters.
To get event logs on other stateless nodes please see :class:`web3.contract.ContractEvents`.
Filter Class
Expand Down Expand Up @@ -106,6 +107,11 @@ will return a new :class:`BlockFilter` object.
new_block_filter = w3.eth.filter('latest')
new_block_filter.get_new_entries()
.. note::

``"safe"`` and ``"finalized"`` block identifiers are not yet supported for
``eth_newBlockFilter``.

.. py:class:: TransactionFilter(...)
``TransactionFilter`` is a subclass of :class:`Filter`.
Expand Down Expand Up @@ -158,7 +164,13 @@ In addition to being order-dependent, there are a few more points to recognize w
- [A, B] "A in first position AND B in second position (and anything after)"
- [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)"

See the JSON-RPC documentation for `eth_newFilter <https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter>`_ more information on the standard filter parameters.
See the JSON-RPC documentation for `eth_newFilter <https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_newfilter>`_ more information on the standard filter parameters.

.. note::

Though ``"latest"`` and ``"safe"`` block identifiers are not yet part of the
specifications for ``eth_newFilter``, they are supported by web3.py and may or
may not yield expected results depending on the node being accessed.

Creating a log filter by either of the above methods will return a :class:`LogFilter` instance.

Expand All @@ -179,7 +191,7 @@ Getting events without setting up a filter
------------------------------------------

You can query an Ethereum node for direct fetch of events, without creating a filter first.
This works on all node types, including Infura.
This works on all node types.

For examples see :meth:`web3.contract.ContractEvents.getLogs`.

Expand Down
46 changes: 27 additions & 19 deletions docs/web3.eth.rst
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,9 @@ The following methods are available on the ``web3.eth`` namespace.

Returns the block specified by ``block_identifier``. Delegates to
``eth_getBlockByNumber`` if ``block_identifier`` is an integer or one of
the predefined block parameters ``'latest', 'earliest', 'pending'``,
otherwise delegates to ``eth_getBlockByHash``. Throws ``BlockNotFound`` error if the block is not found.
the predefined block parameters ``'latest', 'earliest', 'pending',
'safe', 'finalized'`` - otherwise delegates to ``eth_getBlockByHash``.
Throws ``BlockNotFound`` error if the block is not found.

If ``full_transactions`` is ``True`` then the ``'transactions'`` key will
contain full transactions objects. Otherwise it will be an array of
Expand Down Expand Up @@ -478,7 +479,9 @@ The following methods are available on the ``web3.eth`` namespace.
``block_identifier``. Delegates to
``eth_getBlockTransactionCountByNumber`` if ``block_identifier`` is an
integer or one of the predefined block parameters ``'latest', 'earliest',
'pending'``, otherwise delegates to ``eth_getBlockTransactionCountByHash``. Throws ``BlockNotFoundError`` if transactions are not found.
'pending', 'safe', 'finalized'``,
otherwise delegates to ``eth_getBlockTransactionCountByHash``.
Throws ``BlockNotFoundError`` if transactions are not found.

.. code-block:: python
Expand Down Expand Up @@ -580,7 +583,7 @@ The following methods are available on the ``web3.eth`` namespace.
* Delegates to ``eth_getTransactionByHash`` RPC Method

Returns the transaction specified by ``transaction_hash``. If the transaction has not yet been mined throws :class:`web3.exceptions.TransactionNotFound`.
Returns the transaction specified by ``transaction_hash``. If the transaction cannot be found throws :class:`web3.exceptions.TransactionNotFound`.

.. code-block:: python
Expand Down Expand Up @@ -636,7 +639,7 @@ The following methods are available on the ``web3.eth`` namespace.
from the block specified by ``block_identifier``. Delegates to
``eth_getTransactionByBlockNumberAndIndex`` if ``block_identifier`` is an
integer or one of the predefined block parameters ``'latest', 'earliest',
'pending'``, otherwise delegates to
'pending', 'safe', 'finalized'``, otherwise delegates to
``eth_getTransactionByBlockHashAndIndex``.
If a transaction is not found at specified arguments, throws :class:`web3.exceptions.TransactionNotFound`.

Expand Down Expand Up @@ -689,7 +692,7 @@ The following methods are available on the ``web3.eth`` namespace.
from the block specified by ``block_identifier``. Delegates to
``eth_getRawTransactionByBlockNumberAndIndex`` if ``block_identifier`` is an
integer or one of the predefined block parameters ``'latest', 'earliest',
'pending'``, otherwise delegates to
'pending', 'safe', 'finalized'``, otherwise delegates to
``eth_getRawTransactionByBlockHashAndIndex``.
If a transaction is not found at specified arguments, throws :class:`web3.exceptions.TransactionNotFound`.

Expand Down Expand Up @@ -742,7 +745,7 @@ The following methods are available on the ``web3.eth`` namespace.
* Delegates to ``eth_getTransactionReceipt`` RPC Method

Returns the transaction receipt specified by ``transaction_hash``. If the transaction has not yet been mined throws :class:`web3.exceptions.TransactionNotFound`.
Returns the transaction receipt specified by ``transaction_hash``. If the transaction cannot be found throws :class:`web3.exceptions.TransactionNotFound`.

If ``status`` in response equals 1 the transaction was successful. If it is equals 0 the transaction was reverted by EVM.

Expand Down Expand Up @@ -1081,7 +1084,7 @@ The following methods are available on the ``web3.eth`` namespace.
>>> myContract.functions.getVar().call()
1
# The above call equivalent to the raw call:
>>> we3.eth.call({'value': 0, 'gas': 21736, 'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000, 'to': '0xc305c901078781C232A2a521C2aF7980f8385ee9', 'data': '0x477a5c98'})
>>> w3.eth.call({'value': 0, 'gas': 21736, 'maxFeePerGas': 2000000000, 'maxPriorityFeePerGas': 1000000000, 'to': '0xc305c901078781C232A2a521C2aF7980f8385ee9', 'data': '0x477a5c98'})
HexBytes('0x0000000000000000000000000000000000000000000000000000000000000001')
In most cases it is better to make contract function call through the :py:class:`web3.contract.Contract` interface.
Expand Down Expand Up @@ -1217,11 +1220,11 @@ with the filtering API.
dictionary with the following keys.

* ``fromBlock``: ``integer/tag`` - (optional, default: "latest") Integer
block number, or "latest" for the last mined block or "pending",
"earliest" for not yet mined transactions.
block number, or one of predefined block identifiers
"latest", "pending", "earliest", "safe", or "finalized".
* ``toBlock``: ``integer/tag`` - (optional, default: "latest") Integer
block number, or "latest" for the last mined block or "pending",
"earliest" for not yet mined transactions.
block number, or one of predefined block identifiers
"latest", "pending", "earliest", "safe", or "finalized".
* ``address``: ``string`` or list of ``strings``, each 20 Bytes -
(optional) Contract address or a list of addresses from which logs should
originate.
Expand All @@ -1230,6 +1233,11 @@ with the filtering API.
This parameter can also be a list of topic lists in which case filtering
will match any of the provided topic arrays.

.. note::

Though ``"latest"`` and ``"safe"`` block identifiers are not yet part of the
specifications for ``eth_newFilter``, they are supported by web3.py and may or
may not yield expected results depending on the node being accessed.

See :doc:`./filters` for more information about filtering.

Expand All @@ -1251,8 +1259,8 @@ with the filtering API.

.. code-block:: python
>>> filt = web3.eth.filter()
>>> web3.eth.get_filter_changes(filt.filter_id)
>>> filter = web3.eth.filter()
>>> web3.eth.get_filter_changes(filter.filter_id)
[
{
'address': '0xDc3A9Db694BCdd55EBaE4A89B22aC6D12b3F0c24',
Expand Down Expand Up @@ -1284,8 +1292,8 @@ with the filtering API.

.. code-block:: python
>>> filt = web3.eth.filter()
>>> web3.eth.get_filter_logs(filt.filter_id)
>>> filter = web3.eth.filter()
>>> web3.eth.get_filter_logs(filter.filter_id)
[
{
'address': '0xDc3A9Db694BCdd55EBaE4A89B22aC6D12b3F0c24',
Expand Down Expand Up @@ -1318,10 +1326,10 @@ with the filtering API.

.. code-block:: python
>>> filt = web3.eth.filter()
>>> web3.eth.uninstall_filter(filt.filter_id)
>>> filter = web3.eth.filter()
>>> web3.eth.uninstall_filter(filter.filter_id)
True
>>> web3.eth.uninstall_filter(filt.filter_id)
>>> web3.eth.uninstall_filter(filter.filter_id)
False # already uninstalled.
.. py:method:: Eth.uninstallFilter(self, filter_id)
Expand Down
1 change: 1 addition & 0 deletions newsfragments/2655.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add new predefined block identifiers ``safe`` and ``finalized``.
18 changes: 18 additions & 0 deletions tests/core/contracts/test_contract_util_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
validate_payable,
)
from web3.contract import (
parse_block_identifier,
parse_block_identifier_int,
)

Expand All @@ -18,6 +19,23 @@ def test_parse_block_identifier_int(web3):
assert 0 == parse_block_identifier_int(web3, -1 - last_num)


@pytest.mark.parametrize(
"block_identifier,expected_output",
(
(1, 1),
(-1, 0),
("latest", "latest"),
("earliest", "earliest"),
("pending", "pending"),
("safe", "safe"),
("finalized", "finalized"),
),
)
def test_parse_block_identifier_int_and_string(web3, block_identifier, expected_output):
block_id = parse_block_identifier(web3, block_identifier)
assert block_id == expected_output


@pytest.mark.parametrize("value", (0, "0x0", "0x00"))
def test_validate_payable(value):
tx = {"value": value}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
('latest', 'test_predefined'),
('pending', 'test_predefined'),
('earliest', 'test_predefined'),
("safe", "test_predefined"),
("finalized", "test_predefined"),
(-1, ValueError),
(0, 'test_number'),
(1, 'test_number'),
Expand Down
2 changes: 2 additions & 0 deletions tests/core/utilities/test_is_predefined_block_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
('earliest', True),
('latest', True),
('pending', True),
("finalized", True),
("safe", True),
(1, False),
('0x1', False),
),
Expand Down
10 changes: 5 additions & 5 deletions web3/_utils/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@ def is_predefined_block_number(value: Any) -> bool:
value_text = value
elif is_bytes(value):
# `value` could either be random bytes or the utf-8 encoding of
# one of the words in: {"latest", "pending", "earliest"}
# We cannot decode the bytes as utf8, because random bytes likely won't be valid.
# So we speculatively decode as 'latin-1', which cannot fail.
value_text = value.decode('latin-1')
# one of the words in: {"latest", "pending", "earliest", "safe", "finalized"}
# We cannot decode the bytes as utf8, because random bytes likely won't be
# valid. So we speculatively decode as 'latin-1', which cannot fail.
value_text = value.decode("latin-1")
elif is_integer(value):
return False
else:
raise TypeError("unrecognized block reference: %r" % value)

return value_text in {"latest", "pending", "earliest"}
return value_text in {"latest", "pending", "earliest", "safe", "finalized"}


def is_hex_encoded_block_hash(value: Any) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion web3/contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1568,7 +1568,7 @@ def call_contract_function(
def parse_block_identifier(web3: 'Web3', block_identifier: BlockIdentifier) -> BlockIdentifier:
if isinstance(block_identifier, int):
return parse_block_identifier_int(web3, block_identifier)
elif block_identifier in ['latest', 'earliest', 'pending']:
elif block_identifier in {"latest", "earliest", "pending", "safe", "finalized"}:
return block_identifier
elif isinstance(block_identifier, bytes) or is_hex_encoded_block_hash(block_identifier):
return web3.eth.get_block(block_identifier)['number']
Expand Down
2 changes: 1 addition & 1 deletion web3/eth.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ def filter_munger(
if isinstance(filter_params, dict):
return [filter_params]
elif is_string(filter_params):
if filter_params in ['latest', 'pending']:
if filter_params in {'latest', 'pending'}:
return [filter_params]
else:
raise ValueError(
Expand Down
2 changes: 1 addition & 1 deletion web3/providers/eth_tester/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@


def is_named_block(value: Any) -> bool:
return value in {"latest", "earliest", "pending"}
return value in {"latest", "earliest", "pending", "safe", "finalized"}


def is_hexstr(value: Any) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion web3/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
TParams = TypeVar("TParams")
TValue = TypeVar("TValue")

BlockParams = Literal["latest", "earliest", "pending"]
BlockParams = Literal["latest", "earliest", "pending", "safe", "finalized"]
BlockIdentifier = Union[BlockParams, BlockNumber, Hash32, HexStr, HexBytes, int]
LatestBlockParam = Literal["latest"]

Expand Down

0 comments on commit f3b8bf4

Please sign in to comment.