|
| 1 | +use crate::{OpExecutionPayloadEnvelope, PayloadSource}; |
| 2 | +use serde::{Deserialize, Serialize}; |
| 3 | + |
| 4 | +/// Defines the strategy for choosing between the builder block and the L2 client block |
| 5 | +/// during block production. |
| 6 | +#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, clap::ValueEnum)] |
| 7 | +pub enum BlockSelectionPolicy { |
| 8 | + /// Selects the block based on gas usage. |
| 9 | + /// |
| 10 | + /// If the builder block uses less than 10% of the gas used by the L2 client block, |
| 11 | + /// the L2 block is selected instead. This prevents propagation of valid but empty |
| 12 | + /// builder blocks and mitigates issues where the builder is not receiving enough |
| 13 | + /// transactions due to networking or peering failures. |
| 14 | + GasUsed, |
| 15 | +} |
| 16 | + |
| 17 | +impl BlockSelectionPolicy { |
| 18 | + pub fn select_block( |
| 19 | + &self, |
| 20 | + builder_payload: OpExecutionPayloadEnvelope, |
| 21 | + l2_payload: OpExecutionPayloadEnvelope, |
| 22 | + ) -> (OpExecutionPayloadEnvelope, PayloadSource) { |
| 23 | + match self { |
| 24 | + BlockSelectionPolicy::GasUsed => { |
| 25 | + let builder_gas = builder_payload.gas_used() as f64; |
| 26 | + let l2_gas = l2_payload.gas_used() as f64; |
| 27 | + |
| 28 | + // Select the L2 block if the builder block uses less than 10% of the gas. |
| 29 | + // This avoids selecting empty or severely underfilled blocks, |
| 30 | + if builder_gas < l2_gas * 0.1 { |
| 31 | + (l2_payload, PayloadSource::L2) |
| 32 | + } else { |
| 33 | + (builder_payload, PayloadSource::Builder) |
| 34 | + } |
| 35 | + } |
| 36 | + } |
| 37 | + } |
| 38 | +} |
| 39 | + |
| 40 | +#[cfg(test)] |
| 41 | +mod tests { |
| 42 | + use super::*; |
| 43 | + |
| 44 | + use op_alloy_rpc_types_engine::OpExecutionPayloadEnvelopeV4; |
| 45 | + |
| 46 | + #[test] |
| 47 | + fn test_gas_used_policy_select_l2_block() -> eyre::Result<()> { |
| 48 | + let execution_payload = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0","withdrawalsRoot":"0x123400000000000000000000000000000000000000000000000000000000babe"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false,"parentBeaconBlockRoot":"0xdead00000000000000000000000000000000000000000000000000000000beef","executionRequests":["0xdeadbeef"]}"#; |
| 49 | + let mut builder_payload: OpExecutionPayloadEnvelopeV4 = |
| 50 | + serde_json::from_str(execution_payload)?; |
| 51 | + let mut l2_payload = builder_payload.clone(); |
| 52 | + |
| 53 | + let gas_used = 1000000000; |
| 54 | + l2_payload |
| 55 | + .execution_payload |
| 56 | + .payload_inner |
| 57 | + .payload_inner |
| 58 | + .payload_inner |
| 59 | + .gas_used = gas_used; |
| 60 | + |
| 61 | + builder_payload |
| 62 | + .execution_payload |
| 63 | + .payload_inner |
| 64 | + .payload_inner |
| 65 | + .payload_inner |
| 66 | + .gas_used = (gas_used as f64 * 0.09) as u64; |
| 67 | + |
| 68 | + let builder_payload = OpExecutionPayloadEnvelope::V4(builder_payload); |
| 69 | + let l2_payload = OpExecutionPayloadEnvelope::V4(l2_payload); |
| 70 | + |
| 71 | + let selected_payload = |
| 72 | + BlockSelectionPolicy::GasUsed.select_block(builder_payload, l2_payload); |
| 73 | + |
| 74 | + assert_eq!(selected_payload.1, PayloadSource::L2); |
| 75 | + Ok(()) |
| 76 | + } |
| 77 | + |
| 78 | + #[test] |
| 79 | + fn test_gas_used_policy_select_builder_block() -> eyre::Result<()> { |
| 80 | + let execution_payload = r#"{"executionPayload":{"parentHash":"0xe927a1448525fb5d32cb50ee1408461a945ba6c39bd5cf5621407d500ecc8de9","feeRecipient":"0x0000000000000000000000000000000000000000","stateRoot":"0x10f8a0830000e8edef6d00cc727ff833f064b1950afd591ae41357f97e543119","receiptsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","prevRandao":"0xe0d8b4521a7da1582a713244ffb6a86aa1726932087386e2dc7973f43fc6cb24","blockNumber":"0x1","gasLimit":"0x2ffbd2","gasUsed":"0x0","timestamp":"0x1235","extraData":"0xd883010d00846765746888676f312e32312e30856c696e7578","baseFeePerGas":"0x342770c0","blockHash":"0x44d0fa5f2f73a938ebb96a2a21679eb8dea3e7b7dd8fd9f35aa756dda8bf0a8a","transactions":[],"withdrawals":[],"blobGasUsed":"0x0","excessBlobGas":"0x0","withdrawalsRoot":"0x123400000000000000000000000000000000000000000000000000000000babe"},"blockValue":"0x0","blobsBundle":{"commitments":[],"proofs":[],"blobs":[]},"shouldOverrideBuilder":false,"parentBeaconBlockRoot":"0xdead00000000000000000000000000000000000000000000000000000000beef","executionRequests":["0xdeadbeef"]}"#; |
| 81 | + let mut builder_payload: OpExecutionPayloadEnvelopeV4 = |
| 82 | + serde_json::from_str(execution_payload)?; |
| 83 | + let mut l2_payload = builder_payload.clone(); |
| 84 | + |
| 85 | + let gas_used = 1000000000; |
| 86 | + l2_payload |
| 87 | + .execution_payload |
| 88 | + .payload_inner |
| 89 | + .payload_inner |
| 90 | + .payload_inner |
| 91 | + .gas_used = gas_used; |
| 92 | + |
| 93 | + builder_payload |
| 94 | + .execution_payload |
| 95 | + .payload_inner |
| 96 | + .payload_inner |
| 97 | + .payload_inner |
| 98 | + .gas_used = (gas_used as f64 * 0.1) as u64; |
| 99 | + |
| 100 | + let builder_payload = OpExecutionPayloadEnvelope::V4(builder_payload); |
| 101 | + let l2_payload = OpExecutionPayloadEnvelope::V4(l2_payload); |
| 102 | + |
| 103 | + let selected_payload = |
| 104 | + BlockSelectionPolicy::GasUsed.select_block(builder_payload, l2_payload); |
| 105 | + |
| 106 | + assert_eq!(selected_payload.1, PayloadSource::Builder); |
| 107 | + Ok(()) |
| 108 | + } |
| 109 | +} |
0 commit comments