|
| 1 | +""" |
| 2 | +abstract: Tests get blobs engine endpoint for [EIP-4844: Shard Blob Transactions](https://eips.ethereum.org/EIPS/eip-4844) |
| 3 | + Test get blobs engine endpoint for [EIP-4844: Shard Blob Transactions](https://eips.ethereum.org/EIPS/eip-4844). |
| 4 | +
|
| 5 | +""" # noqa: E501 |
| 6 | + |
| 7 | +from typing import List, Optional |
| 8 | + |
| 9 | +import pytest |
| 10 | + |
| 11 | +from ethereum_test_forks import Fork |
| 12 | +from ethereum_test_tools import ( |
| 13 | + Address, |
| 14 | + Alloc, |
| 15 | + Blob, |
| 16 | + BlobsTestFiller, |
| 17 | + NetworkWrappedTransaction, |
| 18 | + Transaction, |
| 19 | + TransactionException, |
| 20 | +) |
| 21 | + |
| 22 | +from ...cancun.eip4844_blobs.common import INF_POINT |
| 23 | +from ...cancun.eip4844_blobs.spec import Spec as Spec4844 |
| 24 | +from ...cancun.eip4844_blobs.spec import SpecHelpers, ref_spec_4844 |
| 25 | + |
| 26 | +CELLS_PER_EXT_BLOB = 128 |
| 27 | + |
| 28 | +REFERENCE_SPEC_GIT_PATH = ref_spec_4844.git_path |
| 29 | +REFERENCE_SPEC_VERSION = ref_spec_4844.version |
| 30 | + |
| 31 | + |
| 32 | +@pytest.fixture |
| 33 | +def destination_account(pre: Alloc) -> Address: |
| 34 | + """Destination account for the blob transactions.""" |
| 35 | + return pre.fund_eoa(amount=0) |
| 36 | + |
| 37 | + |
| 38 | +@pytest.fixture |
| 39 | +def tx_value() -> int: |
| 40 | + """ |
| 41 | + Value contained by the transactions sent during test. |
| 42 | +
|
| 43 | + Can be overloaded by a test case to provide a custom transaction value. |
| 44 | + """ |
| 45 | + return 1 |
| 46 | + |
| 47 | + |
| 48 | +@pytest.fixture |
| 49 | +def tx_gas() -> int: |
| 50 | + """Gas allocated to transactions sent during test.""" |
| 51 | + return 21_000 |
| 52 | + |
| 53 | + |
| 54 | +@pytest.fixture |
| 55 | +def tx_calldata() -> bytes: |
| 56 | + """Calldata in transactions sent during test.""" |
| 57 | + return b"" |
| 58 | + |
| 59 | + |
| 60 | +@pytest.fixture(autouse=True) |
| 61 | +def parent_excess_blobs() -> int: |
| 62 | + """ |
| 63 | + Excess blobs of the parent block. |
| 64 | +
|
| 65 | + Can be overloaded by a test case to provide a custom parent excess blob |
| 66 | + count. |
| 67 | + """ |
| 68 | + return 10 # Defaults to a blob gas price of 1. |
| 69 | + |
| 70 | + |
| 71 | +@pytest.fixture(autouse=True) |
| 72 | +def parent_blobs() -> int: |
| 73 | + """ |
| 74 | + Blobs of the parent blob. |
| 75 | +
|
| 76 | + Can be overloaded by a test case to provide a custom parent blob count. |
| 77 | + """ |
| 78 | + return 0 |
| 79 | + |
| 80 | + |
| 81 | +@pytest.fixture |
| 82 | +def excess_blob_gas( |
| 83 | + fork: Fork, |
| 84 | + parent_excess_blobs: int | None, |
| 85 | + parent_blobs: int | None, |
| 86 | +) -> int | None: |
| 87 | + """ |
| 88 | + Calculate the excess blob gas of the block under test from the parent block. |
| 89 | +
|
| 90 | + Value can be overloaded by a test case to provide a custom excess blob gas. |
| 91 | + """ |
| 92 | + if parent_excess_blobs is None or parent_blobs is None: |
| 93 | + return None |
| 94 | + excess_blob_gas = fork.excess_blob_gas_calculator() |
| 95 | + return excess_blob_gas( |
| 96 | + parent_excess_blobs=parent_excess_blobs, |
| 97 | + parent_blob_count=parent_blobs, |
| 98 | + ) |
| 99 | + |
| 100 | + |
| 101 | +@pytest.fixture |
| 102 | +def blob_gas_price( |
| 103 | + fork: Fork, |
| 104 | + excess_blob_gas: int | None, |
| 105 | +) -> int | None: |
| 106 | + """Return blob gas price for the block of the test.""" |
| 107 | + if excess_blob_gas is None: |
| 108 | + return None |
| 109 | + |
| 110 | + get_blob_gas_price = fork.blob_gas_price_calculator() |
| 111 | + return get_blob_gas_price( |
| 112 | + excess_blob_gas=excess_blob_gas, |
| 113 | + ) |
| 114 | + |
| 115 | + |
| 116 | +@pytest.fixture |
| 117 | +def txs_versioned_hashes(txs_blobs: List[List[Blob]]) -> List[List[bytes]]: |
| 118 | + """List of blob versioned hashes derived from the blobs.""" |
| 119 | + return [[blob.versioned_hash() for blob in blob_tx] for blob_tx in txs_blobs] |
| 120 | + |
| 121 | + |
| 122 | +@pytest.fixture |
| 123 | +def tx_max_fee_per_blob_gas( # noqa: D103 |
| 124 | + blob_gas_price: Optional[int], |
| 125 | +) -> int: |
| 126 | + """ |
| 127 | + Max fee per blob gas for transactions sent during test. |
| 128 | +
|
| 129 | + By default, it is set to the blob gas price of the block. |
| 130 | +
|
| 131 | + Can be overloaded by a test case to test rejection of transactions where |
| 132 | + the max fee per blob gas is insufficient. |
| 133 | + """ |
| 134 | + if blob_gas_price is None: |
| 135 | + # When fork transitioning, the default blob gas price is 1. |
| 136 | + return 1 |
| 137 | + return blob_gas_price |
| 138 | + |
| 139 | + |
| 140 | +@pytest.fixture |
| 141 | +def tx_error() -> Optional[TransactionException]: |
| 142 | + """ |
| 143 | + Even though the final block we are producing in each of these tests is invalid, and some of the |
| 144 | + transactions will be invalid due to the format in the final block, none of the transactions |
| 145 | + should be rejected by the transition tool because they are being sent to it with the correct |
| 146 | + format. |
| 147 | + """ |
| 148 | + return None |
| 149 | + |
| 150 | + |
| 151 | +@pytest.fixture |
| 152 | +def tx_wrapper_version() -> int | None: |
| 153 | + """Return wrapper version used for the transactions sent during test.""" |
| 154 | + return 1 |
| 155 | + |
| 156 | + |
| 157 | +@pytest.fixture(autouse=True) |
| 158 | +def txs( # noqa: D103 |
| 159 | + pre: Alloc, |
| 160 | + destination_account: Optional[Address], |
| 161 | + tx_gas: int, |
| 162 | + tx_value: int, |
| 163 | + tx_calldata: bytes, |
| 164 | + tx_max_fee_per_blob_gas: int, |
| 165 | + txs_versioned_hashes: List[List[bytes]], |
| 166 | + tx_error: Optional[TransactionException], |
| 167 | + txs_blobs: List[List[Blob]], |
| 168 | + tx_wrapper_version: int | None, |
| 169 | +) -> List[NetworkWrappedTransaction | Transaction]: |
| 170 | + """Prepare the list of transactions that are sent during the test.""" |
| 171 | + if len(txs_blobs) != len(txs_versioned_hashes): |
| 172 | + raise ValueError("txs_blobs and txs_versioned_hashes should have the same length") |
| 173 | + txs: List[NetworkWrappedTransaction | Transaction] = [] |
| 174 | + for tx_blobs, tx_versioned_hashes in zip(txs_blobs, txs_versioned_hashes, strict=False): |
| 175 | + tx = Transaction( |
| 176 | + ty=Spec4844.BLOB_TX_TYPE, |
| 177 | + sender=pre.fund_eoa(), |
| 178 | + to=destination_account, |
| 179 | + value=tx_value, |
| 180 | + gas_limit=tx_gas, |
| 181 | + data=tx_calldata, |
| 182 | + max_fee_per_blob_gas=tx_max_fee_per_blob_gas, |
| 183 | + access_list=[], |
| 184 | + blob_versioned_hashes=tx_versioned_hashes, |
| 185 | + error=tx_error, |
| 186 | + ) |
| 187 | + network_wrapped_tx = NetworkWrappedTransaction( |
| 188 | + tx=tx, |
| 189 | + blobs=tx_blobs, |
| 190 | + wrapper_version=tx_wrapper_version, |
| 191 | + ) |
| 192 | + txs.append(network_wrapped_tx) |
| 193 | + return txs |
| 194 | + |
| 195 | + |
| 196 | +def generate_full_blob_tests( |
| 197 | + fork: Fork, |
| 198 | +) -> List: |
| 199 | + """ |
| 200 | + Return a list of tests for invalid blob transactions due to insufficient max fee per blob gas |
| 201 | + parametrized for each different fork. |
| 202 | + """ |
| 203 | + blob_size = Spec4844.FIELD_ELEMENTS_PER_BLOB * SpecHelpers.BYTES_PER_FIELD_ELEMENT |
| 204 | + max_blobs = fork.max_blobs_per_block() |
| 205 | + return [ |
| 206 | + pytest.param( |
| 207 | + [ # Txs |
| 208 | + [ # Blobs per transaction |
| 209 | + Blob( |
| 210 | + data=bytes(blob_size), |
| 211 | + kzg_commitment=INF_POINT, |
| 212 | + kzg_cell_proofs=[INF_POINT] * CELLS_PER_EXT_BLOB, |
| 213 | + ), |
| 214 | + ] |
| 215 | + ], |
| 216 | + id="single_blob_transaction", |
| 217 | + ), |
| 218 | + pytest.param( |
| 219 | + [ # Txs |
| 220 | + [ # Blobs per transaction |
| 221 | + Blob( |
| 222 | + data=bytes(blob_size), |
| 223 | + kzg_commitment=INF_POINT, |
| 224 | + kzg_cell_proofs=[INF_POINT] * CELLS_PER_EXT_BLOB, |
| 225 | + ) |
| 226 | + for _ in range(max_blobs) |
| 227 | + ] |
| 228 | + ], |
| 229 | + id="max_blobs_transaction", |
| 230 | + ), |
| 231 | + pytest.param( |
| 232 | + [ # Txs |
| 233 | + [ # Blobs per transaction |
| 234 | + Blob( |
| 235 | + data=bytes(blob_size), |
| 236 | + kzg_commitment=INF_POINT, |
| 237 | + kzg_cell_proofs=[INF_POINT] * CELLS_PER_EXT_BLOB, |
| 238 | + ) |
| 239 | + ] |
| 240 | + for _ in range(max_blobs) |
| 241 | + ], |
| 242 | + id="single_blob_max_txs", |
| 243 | + ), |
| 244 | + ] |
| 245 | + |
| 246 | + |
| 247 | +@pytest.mark.parametrize_by_fork( |
| 248 | + "txs_blobs", |
| 249 | + generate_full_blob_tests, |
| 250 | +) |
| 251 | +@pytest.mark.exception_test |
| 252 | +@pytest.mark.valid_from("Cancun") |
| 253 | +def test_get_blobs( |
| 254 | + blobs_test: BlobsTestFiller, |
| 255 | + pre: Alloc, |
| 256 | + txs: List[NetworkWrappedTransaction | Transaction], |
| 257 | +): |
| 258 | + """ |
| 259 | + Test valid blob combinations where one or more txs in the block |
| 260 | + serialized version contain a full blob (network version) tx. |
| 261 | + """ |
| 262 | + blobs_test( |
| 263 | + pre=pre, |
| 264 | + txs=txs, |
| 265 | + ) |
0 commit comments