diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4bfe46e1..964a6f92 100755 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -33,7 +33,7 @@ services: <<: *logging op-geth: - image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101304.1 + image: us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101304.2 container_name: op-geth profiles: - op-geth diff --git a/src/config/mod.rs b/src/config/mod.rs index b051fd0b..235e7fd5 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -141,6 +141,8 @@ pub struct ChainConfig { pub max_seq_drift: u64, /// Timestamp of the regolith hardfork pub regolith_time: u64, + /// Timestamp of the canyon hardfork + pub canyon_time: u64, /// Network blocktime #[serde(default = "default_blocktime")] pub blocktime: u64, @@ -252,6 +254,7 @@ impl ChainConfig { max_seq_drift: 600, blocktime: 2, regolith_time: 0, + canyon_time: u64::MAX, } } @@ -289,6 +292,7 @@ impl ChainConfig { seq_window_size: 3600, max_seq_drift: 600, regolith_time: 1679079600, + canyon_time: 1699981200, blocktime: 2, } } @@ -326,6 +330,7 @@ impl ChainConfig { seq_window_size: 3600, max_seq_drift: 600, regolith_time: 0, + canyon_time: 1699981200, blocktime: 2, } } @@ -363,6 +368,7 @@ impl ChainConfig { max_seq_drift: 600, blocktime: 2, regolith_time: 0, + canyon_time: u64::MAX, } } @@ -398,6 +404,7 @@ impl ChainConfig { seq_window_size: 3600, max_seq_drift: 600, regolith_time: 1683219600, + canyon_time: 1699981200, blocktime: 2, } } @@ -476,6 +483,7 @@ pub struct ExternalChainConfig { l1_chain_id: u64, l2_chain_id: u64, regolith_time: u64, + canyon_time: u64, batch_inbox_address: Address, deposit_contract_address: Address, l1_system_config_address: Address, @@ -537,6 +545,7 @@ impl From for ChainConfig { seq_window_size: external.seq_window_size, max_seq_drift: external.max_sequencer_drift, regolith_time: external.regolith_time, + canyon_time: external.canyon_time, blocktime: external.block_time, l2_to_l1_message_passer: addr("0x4200000000000000000000000000000000000016"), } @@ -582,6 +591,7 @@ impl From for ExternalChainConfig { l1_chain_id: chain_config.l1_chain_id, l2_chain_id: chain_config.l2_chain_id, regolith_time: chain_config.regolith_time, + canyon_time: chain_config.canyon_time, batch_inbox_address: chain_config.batch_inbox, deposit_contract_address: chain_config.deposit_contract, l1_system_config_address: chain_config.system_config_contract, @@ -708,6 +718,7 @@ mod test { "l1_chain_id": 900, "l2_chain_id": 901, "regolith_time": 0, + "canyon_time": 0, "batch_inbox_address": "0xff00000000000000000000000000000000000000", "deposit_contract_address": "0x6900000000000000000000000000000000000001", "l1_system_config_address": "0x6900000000000000000000000000000000000009" @@ -754,6 +765,7 @@ mod test { assert_eq!(chain.seq_window_size, 200); assert_eq!(chain.max_seq_drift, 300); assert_eq!(chain.regolith_time, 0); + assert_eq!(chain.canyon_time, 0); assert_eq!(chain.blocktime, 2); assert_eq!( chain.l2_to_l1_message_passer, diff --git a/src/derive/stages/attributes.rs b/src/derive/stages/attributes.rs index ac113ec7..daacffd6 100644 --- a/src/derive/stages/attributes.rs +++ b/src/derive/stages/attributes.rs @@ -74,6 +74,12 @@ impl Attributes { timestamp: l1_info.block_info.timestamp, }); + let withdrawals = if batch.timestamp >= self.config.chain.canyon_time { + Some(Vec::new()) + } else { + None + }; + let timestamp = U64([batch.timestamp]); let l1_inclusion_block = Some(batch.l1_inclusion_block); let seq_number = Some(self.sequence_number); @@ -88,6 +94,7 @@ impl Attributes { transactions, no_tx_pool: true, gas_limit: U64([l1_info.system_config.gas_limit.as_u64()]), + withdrawals, epoch, l1_inclusion_block, seq_number, diff --git a/src/engine/api.rs b/src/engine/api.rs index f3e9cc91..64998c59 100644 --- a/src/engine/api.rs +++ b/src/engine/api.rs @@ -11,11 +11,11 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use crate::engine::DEFAULT_AUTH_PORT; -use crate::engine::ENGINE_GET_PAYLOAD_V1; use super::{ Engine, ExecutionPayload, ForkChoiceUpdate, ForkchoiceState, JwtSecret, PayloadAttributes, - PayloadId, PayloadStatus, ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_NEW_PAYLOAD_V1, + PayloadId, PayloadStatus, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V2, + ENGINE_NEW_PAYLOAD_V2, }; use super::{JSONRPC_VERSION, STATIC_ID}; @@ -217,13 +217,13 @@ impl Engine for EngineApi { }; let forkchoice_state_param = serde_json::to_value(forkchoice_state)?; let params = vec![forkchoice_state_param, payload_attributes_param]; - let res = self.post(ENGINE_FORKCHOICE_UPDATED_V1, params).await?; + let res = self.post(ENGINE_FORKCHOICE_UPDATED_V2, params).await?; Ok(res) } async fn new_payload(&self, execution_payload: ExecutionPayload) -> Result { let params = vec![serde_json::to_value(execution_payload)?]; - let res = self.post(ENGINE_NEW_PAYLOAD_V1, params).await?; + let res = self.post(ENGINE_NEW_PAYLOAD_V2, params).await?; Ok(res) } @@ -231,11 +231,19 @@ impl Engine for EngineApi { let encoded = format!("{:x}", payload_id); let padded = format!("0x{:0>16}", encoded); let params = vec![Value::String(padded)]; - let res = self.post(ENGINE_GET_PAYLOAD_V1, params).await?; - Ok(res) + let res = self + .post::(ENGINE_GET_PAYLOAD_V2, params) + .await?; + Ok(res.execution_payload) } } +#[derive(Debug, Serialize, Deserialize, Default)] +#[serde(rename_all = "camelCase")] +struct GetPayloadResponse { + execution_payload: ExecutionPayload, +} + #[cfg(test)] mod tests { use std::time::SystemTime; diff --git a/src/engine/payload.rs b/src/engine/payload.rs index a4107dba..52b75125 100644 --- a/src/engine/payload.rs +++ b/src/engine/payload.rs @@ -39,6 +39,9 @@ pub struct ExecutionPayload { pub block_hash: H256, /// An array of transaction objects where each object is a byte list pub transactions: Vec, + /// An array of beaconchain withdrawals. Always empty as this exists only for L1 compatibility + #[serde(skip_serializing_if = "Option::is_none")] + pub withdrawals: Option>, } impl TryFrom> for ExecutionPayload { @@ -71,6 +74,7 @@ impl TryFrom> for ExecutionPayload { .into(), block_hash: value.hash.unwrap(), transactions: encoded_txs, + withdrawals: Some(Vec::new()), }) } } @@ -97,6 +101,10 @@ pub struct PayloadAttributes { /// This field overrides the gas limit used during block-building. /// If not specified as rollup, a STATUS_INVALID is returned. pub gas_limit: U64, + /// Beaconchain withdrawals. This exists only for compatibility with L1, and is not used. Prior + /// to Canyon, this value is always None. After Canyon it is an empty array. Note that we use + /// the () type here since we never have a non empty array. + pub withdrawals: Option>, /// The batch epoch number from derivation. This value is not expected by the engine is skipped /// during serialization and deserialization. #[serde(skip)] diff --git a/src/engine/types.rs b/src/engine/types.rs index a3b563e8..9bae9bf2 100644 --- a/src/engine/types.rs +++ b/src/engine/types.rs @@ -10,22 +10,19 @@ pub const STATIC_ID: u32 = 1; pub const JSONRPC_VERSION: &str = "2.0"; /// The new payload method string -pub const ENGINE_NEW_PAYLOAD_V1: &str = "engine_newPayloadV1"; -// pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2"; +pub const ENGINE_NEW_PAYLOAD_V2: &str = "engine_newPayloadV2"; /// The new payload timeout pub const ENGINE_NEW_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(8); /// The get payload method string -pub const ENGINE_GET_PAYLOAD_V1: &str = "engine_getPayloadV1"; -// pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2"; +pub const ENGINE_GET_PAYLOAD_V2: &str = "engine_getPayloadV2"; /// The get payload timeout pub const ENGINE_GET_PAYLOAD_TIMEOUT: Duration = Duration::from_secs(2); /// The forkchoice updated method string -pub const ENGINE_FORKCHOICE_UPDATED_V1: &str = "engine_forkchoiceUpdatedV1"; -// pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2"; +pub const ENGINE_FORKCHOICE_UPDATED_V2: &str = "engine_forkchoiceUpdatedV2"; /// The forkchoice updated timeout pub const ENGINE_FORKCHOICE_UPDATED_TIMEOUT: Duration = Duration::from_secs(8); diff --git a/src/network/handlers/block_handler.rs b/src/network/handlers/block_handler.rs index 3b4dc499..b1c45a5a 100644 --- a/src/network/handlers/block_handler.rs +++ b/src/network/handlers/block_handler.rs @@ -16,13 +16,23 @@ pub struct BlockHandler { chain_id: u64, block_sender: Sender, unsafe_signer_recv: watch::Receiver
, + blocks_v1_topic: IdentTopic, + blocks_v2_topic: IdentTopic, } impl Handler for BlockHandler { fn handle(&self, msg: Message) -> MessageAcceptance { tracing::debug!("received block"); - match decode_block_msg(msg.data) { + let decoded = if msg.topic == self.blocks_v1_topic.hash() { + decode_block_msg::(msg.data) + } else if msg.topic == self.blocks_v2_topic.hash() { + decode_block_msg::(msg.data) + } else { + return MessageAcceptance::Reject; + }; + + match decoded { Ok((payload, signature, payload_hash)) => { if self.block_valid(&payload, signature, payload_hash) { _ = self.block_sender.send(payload); @@ -39,8 +49,8 @@ impl Handler for BlockHandler { } } - fn topic(&self) -> TopicHash { - IdentTopic::new(format!("/optimism/{}/0/blocks", self.chain_id)).into() + fn topics(&self) -> Vec { + vec![self.blocks_v1_topic.hash(), self.blocks_v2_topic.hash()] } } @@ -55,6 +65,8 @@ impl BlockHandler { chain_id, block_sender: sender, unsafe_signer_recv: unsafe_recv, + blocks_v1_topic: IdentTopic::new(format!("/optimism/{}/0/blocks", chain_id)), + blocks_v2_topic: IdentTopic::new(format!("/optimism/{}/1/blocks", chain_id)), }; (handler, recv) @@ -83,7 +95,11 @@ impl BlockHandler { } } -fn decode_block_msg(data: Vec) -> Result<(ExecutionPayload, Signature, PayloadHash)> { +fn decode_block_msg(data: Vec) -> Result<(ExecutionPayload, Signature, PayloadHash)> +where + T: SimpleSerialize, + ExecutionPayload: From, +{ let mut decoder = snap::raw::Decoder::new(); let decompressed = decoder.decompress_vec(&data)?; let sig_data = &decompressed[..65]; @@ -91,7 +107,7 @@ fn decode_block_msg(data: Vec) -> Result<(ExecutionPayload, Signature, Paylo let signature = Signature::try_from(sig_data)?; - let payload: ExecutionPayloadSSZ = deserialize(block_data)?; + let payload: T = deserialize(block_data)?; let payload: ExecutionPayload = ExecutionPayload::from(payload); let payload_hash = PayloadHash::from(block_data); @@ -129,7 +145,7 @@ type VecAddress = Vector; type Transaction = List; #[derive(SimpleSerialize, Default)] -struct ExecutionPayloadSSZ { +struct ExecutionPayloadV1SSZ { pub parent_hash: Bytes32, pub fee_recipient: VecAddress, pub state_root: Bytes32, @@ -146,8 +162,57 @@ struct ExecutionPayloadSSZ { pub transactions: List, } -impl From for ExecutionPayload { - fn from(value: ExecutionPayloadSSZ) -> Self { +impl From for ExecutionPayload { + fn from(value: ExecutionPayloadV1SSZ) -> Self { + Self { + parent_hash: convert_hash(value.parent_hash), + fee_recipient: convert_address(value.fee_recipient), + state_root: convert_hash(value.state_root), + receipts_root: convert_hash(value.receipts_root), + logs_bloom: convert_byte_vector(value.logs_bloom), + prev_randao: convert_hash(value.prev_randao), + block_number: value.block_number.into(), + gas_limit: value.gas_limit.into(), + gas_used: value.gas_used.into(), + timestamp: value.timestamp.into(), + extra_data: convert_byte_list(value.extra_data), + base_fee_per_gas: convert_uint(value.base_fee_per_gas), + block_hash: convert_hash(value.block_hash), + transactions: convert_tx_list(value.transactions), + withdrawals: None, + } + } +} + +#[derive(SimpleSerialize, Default)] +struct ExecutionPayloadV2SSZ { + pub parent_hash: Bytes32, + pub fee_recipient: VecAddress, + pub state_root: Bytes32, + pub receipts_root: Bytes32, + pub logs_bloom: Vector, + pub prev_randao: Bytes32, + pub block_number: u64, + pub gas_limit: u64, + pub gas_used: u64, + pub timestamp: u64, + pub extra_data: List, + pub base_fee_per_gas: U256, + pub block_hash: Bytes32, + pub transactions: List, + pub withdrawals: List, +} + +#[derive(SimpleSerialize, Default)] +struct Withdrawal { + index: u64, + validator_index: u64, + address: VecAddress, + amount: u64, +} + +impl From for ExecutionPayload { + fn from(value: ExecutionPayloadV2SSZ) -> Self { Self { parent_hash: convert_hash(value.parent_hash), fee_recipient: convert_address(value.fee_recipient), @@ -163,6 +228,7 @@ impl From for ExecutionPayload { base_fee_per_gas: convert_uint(value.base_fee_per_gas), block_hash: convert_hash(value.block_hash), transactions: convert_tx_list(value.transactions), + withdrawals: Some(Vec::new()), } } } diff --git a/src/network/handlers/mod.rs b/src/network/handlers/mod.rs index 8f7db755..3ec9cb7b 100644 --- a/src/network/handlers/mod.rs +++ b/src/network/handlers/mod.rs @@ -4,5 +4,5 @@ pub mod block_handler; pub trait Handler: Send { fn handle(&self, msg: Message) -> MessageAcceptance; - fn topic(&self) -> TopicHash; + fn topics(&self) -> Vec; } diff --git a/src/network/service/discovery.rs b/src/network/service/discovery.rs index 82771992..85c70d39 100644 --- a/src/network/service/discovery.rs +++ b/src/network/service/discovery.rs @@ -44,7 +44,7 @@ pub fn start(addr: NetworkAddress, chain_id: u64) -> Result> { } } - sleep(Duration::from_secs(30)).await; + sleep(Duration::from_secs(10)).await; } }); diff --git a/src/network/service/mod.rs b/src/network/service/mod.rs index 1c3f13da..68fdaf2a 100644 --- a/src/network/service/mod.rs +++ b/src/network/service/mod.rs @@ -155,11 +155,17 @@ impl Behaviour { handlers .iter() - .map(|handler| { - let topic = IdentTopic::new(handler.topic().into_string()); - gossipsub - .subscribe(&topic) - .map_err(|_| eyre::eyre!("subscription failed")) + .flat_map(|handler| { + handler + .topics() + .iter() + .map(|topic| { + let topic = IdentTopic::new(topic.to_string()); + gossipsub + .subscribe(&topic) + .map_err(|_| eyre::eyre!("subscription failed")) + }) + .collect::>() }) .collect::>>()?; @@ -180,7 +186,9 @@ impl Event { message, }) = self { - let handler = handlers.iter().find(|h| h.topic() == message.topic); + let handler = handlers + .iter() + .find(|h| h.topics().contains(&message.topic)); if let Some(handler) = handler { let status = handler.handle(message);