|
| 1 | +"""Test execution format to get blobs from the execution client.""" |
| 2 | + |
| 3 | +from typing import ClassVar, Dict, List |
| 4 | + |
| 5 | +from ethereum_test_base_types import Hash |
| 6 | +from ethereum_test_forks import Fork |
| 7 | +from ethereum_test_rpc import BlobAndProofV1, BlobAndProofV2, EngineRPC, EthRPC |
| 8 | +from ethereum_test_types import NetworkWrappedTransaction, Transaction |
| 9 | + |
| 10 | +from .base import BaseExecute |
| 11 | + |
| 12 | + |
| 13 | +def versioned_hashes_with_blobs_and_proofs( |
| 14 | + tx: NetworkWrappedTransaction, |
| 15 | +) -> Dict[Hash, BlobAndProofV1 | BlobAndProofV2]: |
| 16 | + """ |
| 17 | + Return a dictionary of versioned hashes with their corresponding blobs and |
| 18 | + proofs. |
| 19 | + """ |
| 20 | + versioned_hashes: Dict[Hash, BlobAndProofV1 | BlobAndProofV2] = {} |
| 21 | + for blob in tx.blobs: |
| 22 | + versioned_hash = blob.versioned_hash() |
| 23 | + if blob.kzg_proof is not None: |
| 24 | + versioned_hashes[versioned_hash] = BlobAndProofV1(blob=blob.data, proof=blob.kzg_proof) |
| 25 | + elif blob.kzg_cell_proofs is not None: |
| 26 | + versioned_hashes[versioned_hash] = BlobAndProofV2( |
| 27 | + blob=blob.data, proofs=blob.kzg_cell_proofs |
| 28 | + ) |
| 29 | + else: |
| 30 | + raise ValueError( |
| 31 | + f"Blob with versioned hash {versioned_hash.hex()} requires either kzg_proof " |
| 32 | + "or kzg_cell_proofs, but both are None" |
| 33 | + ) |
| 34 | + |
| 35 | + return versioned_hashes |
| 36 | + |
| 37 | + |
| 38 | +class BlobTransaction(BaseExecute): |
| 39 | + """ |
| 40 | + Represents a test execution format to send blob transactions to the client and then |
| 41 | + use `engine_getBlobsV*` end points to validate the proofs generated by the execution client. |
| 42 | + """ |
| 43 | + |
| 44 | + format_name: ClassVar[str] = "blob_transaction_test" |
| 45 | + description: ClassVar[str] = ( |
| 46 | + "Send blob transactions to the execution client and validate their availability via " |
| 47 | + "`engine_getBlobsV*`" |
| 48 | + ) |
| 49 | + requires_engine_rpc: ClassVar[bool] = True |
| 50 | + |
| 51 | + txs: List[NetworkWrappedTransaction | Transaction] |
| 52 | + |
| 53 | + def execute(self, fork: Fork, eth_rpc: EthRPC, engine_rpc: EngineRPC | None): |
| 54 | + """Execute the format.""" |
| 55 | + assert engine_rpc is not None, "Engine RPC is required for this format." |
| 56 | + versioned_hashes: Dict[Hash, BlobAndProofV1 | BlobAndProofV2] = {} |
| 57 | + sent_txs: List[Transaction] = [] |
| 58 | + for tx in self.txs: |
| 59 | + if isinstance(tx, NetworkWrappedTransaction): |
| 60 | + tx.tx = tx.tx.with_signature_and_sender() |
| 61 | + sent_txs.append(tx.tx) |
| 62 | + expected_hash = tx.tx.hash |
| 63 | + versioned_hashes.update(versioned_hashes_with_blobs_and_proofs(tx)) |
| 64 | + else: |
| 65 | + tx = tx.with_signature_and_sender() |
| 66 | + sent_txs.append(tx) |
| 67 | + expected_hash = tx.hash |
| 68 | + received_hash = eth_rpc.send_raw_transaction(tx.rlp()) |
| 69 | + assert expected_hash == received_hash, ( |
| 70 | + f"Expected hash {expected_hash} does not match received hash {received_hash}." |
| 71 | + ) |
| 72 | + version = fork.engine_get_blobs_version() |
| 73 | + assert version is not None, "Engine get blobs version is not supported by the fork." |
| 74 | + blob_response = engine_rpc.get_blobs(list(versioned_hashes.keys()), version=version) |
| 75 | + local_blobs_and_proofs = list(versioned_hashes.values()) |
| 76 | + if len(blob_response) != len(local_blobs_and_proofs): |
| 77 | + raise ValueError( |
| 78 | + f"Expected {len(local_blobs_and_proofs)} blobs and proofs, " |
| 79 | + f"got {len(blob_response)}." |
| 80 | + ) |
| 81 | + for expected_blob, received_blob in zip( |
| 82 | + local_blobs_and_proofs, blob_response.root, strict=False |
| 83 | + ): |
| 84 | + if received_blob is None: |
| 85 | + raise ValueError("Received blob is empty.") |
| 86 | + if isinstance(expected_blob, BlobAndProofV1): |
| 87 | + if not isinstance(received_blob, BlobAndProofV1): |
| 88 | + raise ValueError("Received blob is not a BlobAndProofV1.") |
| 89 | + if expected_blob.blob != received_blob.blob: |
| 90 | + raise ValueError("Blob mismatch.") |
| 91 | + if expected_blob.proof != received_blob.proof: |
| 92 | + raise ValueError("Proof mismatch.") |
| 93 | + elif isinstance(expected_blob, BlobAndProofV2): |
| 94 | + if not isinstance(received_blob, BlobAndProofV2): |
| 95 | + raise ValueError("Received blob is not a BlobAndProofV2.") |
| 96 | + if expected_blob.blob != received_blob.blob: |
| 97 | + raise ValueError("Blob mismatch.") |
| 98 | + if expected_blob.proofs != received_blob.proofs: |
| 99 | + raise ValueError("Proofs mismatch.") |
| 100 | + else: |
| 101 | + raise ValueError(f"Unexpected blob type: {type(expected_blob)}") |
| 102 | + |
| 103 | + eth_rpc.wait_for_transactions(sent_txs) |
0 commit comments