From ed7354d46078c16edb7cf5be7474071644d23e09 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Mon, 2 Feb 2026 21:46:10 -0800 Subject: [PATCH] Payload envelope db operations (#8717) Adds support for payload envelopes in the db. This is the minimum we'll need to store and fetch payloads. Co-Authored-By: Eitan Seri- Levi --- beacon_node/beacon_chain/src/beacon_chain.rs | 7 ++ beacon_node/beacon_chain/src/migrate.rs | 1 + .../beacon_chain/tests/schema_stability.rs | 4 +- beacon_node/store/src/hot_cold_store.rs | 72 +++++++++++++++++++ beacon_node/store/src/impls.rs | 1 + .../signed_execution_payload_envelope.rs | 18 +++++ beacon_node/store/src/lib.rs | 8 ++- 7 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 beacon_node/store/src/impls/signed_execution_payload_envelope.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index c29aa10e73..9d16296f77 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -1296,6 +1296,13 @@ impl BeaconChain { Ok(self.store.get_blinded_block(block_root)?) } + pub fn get_payload_envelope( + &self, + block_root: &Hash256, + ) -> Result>, Error> { + Ok(self.store.get_payload_envelope(block_root)?) + } + /// Return the status of a block as it progresses through the various caches of the beacon /// chain. Used by sync to learn the status of a block and prevent repeated downloads / /// processing attempts. diff --git a/beacon_node/beacon_chain/src/migrate.rs b/beacon_node/beacon_chain/src/migrate.rs index bd232f2e8a..24258d2d31 100644 --- a/beacon_node/beacon_chain/src/migrate.rs +++ b/beacon_node/beacon_chain/src/migrate.rs @@ -773,6 +773,7 @@ impl, Cold: ItemStore> BackgroundMigrator = DBColumn::iter().map(|c| c.as_str()).collect(); let expected_columns = vec![ "bma", "blk", "blb", "bdc", "bdi", "ste", "hsd", "hsn", "bsn", "bsd", "bss", "bs3", "bcs", - "bst", "exp", "bch", "opo", "etc", "frk", "pkc", "brp", "bsx", "bsr", "bbx", "bbr", "bhr", - "brm", "dht", "cus", "otb", "bhs", "olc", "lcu", "scb", "scm", "dmy", + "bst", "exp", "pay", "bch", "opo", "etc", "frk", "pkc", "brp", "bsx", "bsr", "bbx", "bbr", + "bhr", "brm", "dht", "cus", "otb", "bhs", "olc", "lcu", "scb", "scm", "dmy", ]; assert_eq!(expected_columns, current_columns); } diff --git a/beacon_node/store/src/hot_cold_store.rs b/beacon_node/store/src/hot_cold_store.rs index 9a5a88979d..6e165702a2 100644 --- a/beacon_node/store/src/hot_cold_store.rs +++ b/beacon_node/store/src/hot_cold_store.rs @@ -745,6 +745,32 @@ impl, Cold: ItemStore> HotColdDB .map_err(|e| e.into()) } + pub fn get_payload_envelope( + &self, + block_root: &Hash256, + ) -> Result>, Error> { + let key = block_root.as_slice(); + + match self + .hot_db + .get_bytes(SignedExecutionPayloadEnvelope::::db_column(), key)? + { + Some(bytes) => { + let envelope = SignedExecutionPayloadEnvelope::from_ssz_bytes(&bytes)?; + Ok(Some(envelope)) + } + None => Ok(None), + } + } + + /// Check if the payload envelope for a block exists on disk. + pub fn payload_envelope_exists(&self, block_root: &Hash256) -> Result { + self.hot_db.key_exists( + SignedExecutionPayloadEnvelope::::db_column(), + block_root.as_slice(), + ) + } + /// Load the execution payload for a block from disk. /// This method deserializes with the proper fork. pub fn get_execution_payload( @@ -1027,6 +1053,33 @@ impl, Cold: ItemStore> HotColdDB } } + // TODO(gloas) we should store the execution payload separately like we do for blocks. + /// Prepare a signed execution payload envelope for storage in the database. + pub fn payload_envelope_as_kv_store_ops( + &self, + key: &Hash256, + payload: &SignedExecutionPayloadEnvelope, + ops: &mut Vec, + ) { + ops.push(KeyValueStoreOp::PutKeyValue( + SignedExecutionPayloadEnvelope::::db_column(), + key.as_slice().into(), + payload.as_ssz_bytes(), + )); + } + + pub fn put_payload_envelope( + &self, + block_root: &Hash256, + payload_envelope: SignedExecutionPayloadEnvelope, + ) -> Result<(), Error> { + self.hot_db.put_bytes( + SignedExecutionPayloadEnvelope::::db_column(), + block_root.as_slice(), + &payload_envelope.as_ssz_bytes(), + ) + } + /// Store a state in the store. pub fn put_state(&self, state_root: &Hash256, state: &BeaconState) -> Result<(), Error> { let mut ops: Vec = Vec::new(); @@ -1283,6 +1336,14 @@ impl, Cold: ItemStore> HotColdDB ); } + StoreOp::PutPayloadEnvelope(block_root, payload_envelope) => { + self.payload_envelope_as_kv_store_ops( + &block_root, + &payload_envelope, + &mut key_value_batch, + ); + } + StoreOp::PutStateSummary(state_root, summary) => { key_value_batch.push(summary.as_kv_store_op(state_root)); } @@ -1309,6 +1370,13 @@ impl, Cold: ItemStore> HotColdDB } } + StoreOp::DeletePayloadEnvelope(block_root) => { + key_value_batch.push(KeyValueStoreOp::DeleteKey( + SignedExecutionPayloadEnvelope::::db_column(), + block_root.as_slice().to_vec(), + )) + } + StoreOp::DeleteState(state_root, slot) => { // Delete the hot state summary. key_value_batch.push(KeyValueStoreOp::DeleteKey( @@ -1528,6 +1596,8 @@ impl, Cold: ItemStore> HotColdDB StoreOp::PutDataColumns(_, _) => (), + StoreOp::PutPayloadEnvelope(_, _) => (), + StoreOp::PutState(_, _) => (), StoreOp::PutStateSummary(_, _) => (), @@ -1536,6 +1606,8 @@ impl, Cold: ItemStore> HotColdDB guard.delete_block(&block_root); } + StoreOp::DeletePayloadEnvelope(_) => (), + StoreOp::DeleteState(_, _) => (), StoreOp::DeleteBlobs(_) => (), diff --git a/beacon_node/store/src/impls.rs b/beacon_node/store/src/impls.rs index 691c79ace7..a2b2f3b2d6 100644 --- a/beacon_node/store/src/impls.rs +++ b/beacon_node/store/src/impls.rs @@ -1 +1,2 @@ pub mod execution_payload; +mod signed_execution_payload_envelope; diff --git a/beacon_node/store/src/impls/signed_execution_payload_envelope.rs b/beacon_node/store/src/impls/signed_execution_payload_envelope.rs new file mode 100644 index 0000000000..3faab4b7d5 --- /dev/null +++ b/beacon_node/store/src/impls/signed_execution_payload_envelope.rs @@ -0,0 +1,18 @@ +use ssz::{Decode, Encode}; +use types::{EthSpec, SignedExecutionPayloadEnvelope}; + +use crate::{DBColumn, Error, StoreItem}; + +impl StoreItem for SignedExecutionPayloadEnvelope { + fn db_column() -> DBColumn { + DBColumn::PayloadEnvelope + } + + fn as_store_bytes(&self) -> Vec { + self.as_ssz_bytes() + } + + fn from_store_bytes(bytes: &[u8]) -> Result { + Ok(Self::from_ssz_bytes(bytes)?) + } +} diff --git a/beacon_node/store/src/lib.rs b/beacon_node/store/src/lib.rs index 83ca43ebaa..ee9cfce0ec 100644 --- a/beacon_node/store/src/lib.rs +++ b/beacon_node/store/src/lib.rs @@ -234,12 +234,14 @@ pub enum StoreOp<'a, E: EthSpec> { PutState(Hash256, &'a BeaconState), PutBlobs(Hash256, BlobSidecarList), PutDataColumns(Hash256, DataColumnSidecarList), + PutPayloadEnvelope(Hash256, Arc>), PutStateSummary(Hash256, HotStateSummary), DeleteBlock(Hash256), DeleteBlobs(Hash256), DeleteDataColumns(Hash256, Vec, ForkName), DeleteState(Hash256, Option), DeleteExecutionPayload(Hash256), + DeletePayloadEnvelope(Hash256), DeleteSyncCommitteeBranch(Hash256), KeyValueOp(KeyValueStoreOp), } @@ -310,6 +312,9 @@ pub enum DBColumn { /// Execution payloads for blocks more recent than the finalized checkpoint. #[strum(serialize = "exp")] ExecPayload, + /// Post-gloas execution payload envelopes. + #[strum(serialize = "pay")] + PayloadEnvelope, /// For persisting in-memory state to the database. #[strum(serialize = "bch")] BeaconChain, @@ -421,7 +426,8 @@ impl DBColumn { | Self::BeaconRestorePoint | Self::DhtEnrs | Self::CustodyContext - | Self::OptimisticTransitionBlock => 32, + | Self::OptimisticTransitionBlock + | Self::PayloadEnvelope => 32, Self::BeaconBlockRoots | Self::BeaconDataColumnCustodyInfo | Self::BeaconBlockRootsChunked