Repository: ethereum/web3.py (python)

Issue:
Add new predefined block identifiers
### What was wrong?

There are [new predefined block identifiers](https://github.com/ethereum/execution-apis/blob/830fcd8768ef46353c8a0e3b8df85af421026a98/src/schemas/block.yaml#L97-L98), "finalized" and "safe" that should be added to the current {"latest", "pending", "earliest"} set.


### How can it be fixed?

Search the codebase and add them in where the other predefined block numbers are. Should also add tests to make sure everything works as expected.

Pull request description:
[v5] Add support for ``safe`` and ``finalized`` block identifiers
### What was wrong?

Closes #2641
`v5` version of PR #2652 

**Note: I didn't add the integration tests here because we don't expect to update `v5` test fixtures to post-merge. Only the relevant tests from PR #2652 were added.**

### Todo:
- [x] Add entry to the [release notes](https://github.com/ethereum/web3.py/blob/master/newsfragments/README.md)

#### Cute Animal Picture

![Put a link to a cute animal picture inside the parenthesis-->](https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fpbs.twimg.com%2Fmedia%2FDoxa4c5U0AACgRC.jpg%3Alarge&f=1&nofb=1)


Patch:
diff --git a/docs/filters.rst b/docs/filters.rst
index ae460473..c6384993 100644
--- a/docs/filters.rst
+++ b/docs/filters.rst
@@ -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')``
 
@@ -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
@@ -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`.
@@ -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.
 
@@ -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`.
 
diff --git a/docs/web3.eth.rst b/docs/web3.eth.rst
index 7d5a4737..8b7de7bf 100644
--- a/docs/web3.eth.rst
+++ b/docs/web3.eth.rst
@@ -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
@@ -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
 
@@ -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
 
@@ -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`.
 
@@ -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`.
 
@@ -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.
 
@@ -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.
@@ -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.
@@ -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.
 
@@ -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',
@@ -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',
@@ -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)
diff --git a/newsfragments/2655.feature.rst b/newsfragments/2655.feature.rst
new file mode 100644
index 00000000..75e1e48f
--- /dev/null
+++ b/newsfragments/2655.feature.rst
@@ -0,0 +1,1 @@
+Add new predefined block identifiers ``safe`` and ``finalized``.
diff --git a/web3/_utils/blocks.py b/web3/_utils/blocks.py
index ac875f3c..23a443e3 100644
--- a/web3/_utils/blocks.py
+++ b/web3/_utils/blocks.py
@@ -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:
diff --git a/web3/contract.py b/web3/contract.py
index 38e51a5e..8efc2791 100644
--- a/web3/contract.py
+++ b/web3/contract.py
@@ -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']
diff --git a/web3/eth.py b/web3/eth.py
index 6148134f..9ab4031d 100644
--- a/web3/eth.py
+++ b/web3/eth.py
@@ -888,7 +888,7 @@ class Eth(BaseEth):
         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(
diff --git a/web3/types.py b/web3/types.py
index fa9ddac2..ee4ab45a 100644
--- a/web3/types.py
+++ b/web3/types.py
@@ -44,7 +44,7 @@ TReturn = TypeVar("TReturn")
 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"]
 


Test patch:
diff --git a/tests/core/contracts/test_contract_util_functions.py b/tests/core/contracts/test_contract_util_functions.py
index ed9fad0b..9b2a3df1 100644
--- a/tests/core/contracts/test_contract_util_functions.py
+++ b/tests/core/contracts/test_contract_util_functions.py
@@ -4,6 +4,7 @@ from web3._utils.contracts import (
     validate_payable,
 )
 from web3.contract import (
+    parse_block_identifier,
     parse_block_identifier_int,
 )
 
@@ -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}
diff --git a/tests/core/core/block-utils/test_select_method_for_block_identifier.py b/tests/core/core/block-utils/test_select_method_for_block_identifier.py
index 8bcebb37..9facbb05 100644
--- a/tests/core/core/block-utils/test_select_method_for_block_identifier.py
+++ b/tests/core/core/block-utils/test_select_method_for_block_identifier.py
@@ -22,6 +22,8 @@ selector_fn = partial(
         ('latest', 'test_predefined'),
         ('pending', 'test_predefined'),
         ('earliest', 'test_predefined'),
+        ("safe", "test_predefined"),
+        ("finalized", "test_predefined"),
         (-1, ValueError),
         (0, 'test_number'),
         (1, 'test_number'),
diff --git a/tests/core/utilities/test_is_predefined_block_number.py b/tests/core/utilities/test_is_predefined_block_number.py
index 95782ea4..0db187dd 100644
--- a/tests/core/utilities/test_is_predefined_block_number.py
+++ b/tests/core/utilities/test_is_predefined_block_number.py
@@ -11,6 +11,8 @@ from web3._utils.blocks import (
         ('earliest', True),
         ('latest', True),
         ('pending', True),
+        ("finalized", True),
+        ("safe", True),
         (1, False),
         ('0x1', False),
     ),
diff --git a/web3/providers/eth_tester/middleware.py b/web3/providers/eth_tester/middleware.py
index fb865d8a..605455da 100644
--- a/web3/providers/eth_tester/middleware.py
+++ b/web3/providers/eth_tester/middleware.py
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
 
 
 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: