use borsh::{BorshDeserialize, BorshSerialize}; use common::{HashType, block::BlockMeta}; use lee::V03State; use crate::{ CF_META_NAME, DbResult, cells::{SimpleReadableCell, SimpleStorableCell, SimpleWritableCell}, error::DbError, sequencer::{ CF_LEE_STATE_NAME, DB_FINAL_BLOCK_META_KEY, DB_FINAL_LEE_STATE_KEY, DB_LEE_STATE_KEY, DB_META_CROSS_ZONE_PEER_FLOOR_KEY, DB_META_LAST_FINALIZED_BLOCK_ID, DB_META_LATEST_BLOCK_META_KEY, DB_META_PENDING_CROSS_ZONE_DISPATCHES_KEY, DB_META_PENDING_DEPOSIT_EVENTS_KEY, DB_META_UNSEEN_WITHDRAW_COUNT_KEY, DB_META_ZONE_CURSOR_KEY, DB_META_ZONE_SDK_CHECKPOINT_KEY, }, }; #[derive(BorshDeserialize)] pub struct LEEStateCellOwned(pub V03State); impl SimpleStorableCell for LEEStateCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_LEE_STATE_KEY; const CF_NAME: &'static str = CF_LEE_STATE_NAME; } impl SimpleReadableCell for LEEStateCellOwned {} #[derive(BorshSerialize)] pub struct LEEStateCellRef<'state>(pub &'state V03State); impl SimpleStorableCell for LEEStateCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_LEE_STATE_KEY; const CF_NAME: &'static str = CF_LEE_STATE_NAME; } impl SimpleWritableCell for LEEStateCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message(err, Some("Failed to serialize last state".to_owned())) }) } } /// State at the last L1-finalized block, written atomically with /// [`FinalBlockMetaCellRef`]. #[derive(BorshDeserialize)] pub struct FinalLeeStateCellOwned(pub V03State); impl SimpleStorableCell for FinalLeeStateCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_FINAL_LEE_STATE_KEY; const CF_NAME: &'static str = CF_LEE_STATE_NAME; } impl SimpleReadableCell for FinalLeeStateCellOwned {} #[derive(BorshSerialize)] pub struct FinalLeeStateCellRef<'state>(pub &'state V03State); impl SimpleStorableCell for FinalLeeStateCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_FINAL_LEE_STATE_KEY; const CF_NAME: &'static str = CF_LEE_STATE_NAME; } impl SimpleWritableCell for FinalLeeStateCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message(err, Some("Failed to serialize final state".to_owned())) }) } } /// `(id, hash)` of the last L1-finalized block, paired with [`FinalLeeStateCellRef`]. #[derive(BorshDeserialize)] pub struct FinalBlockMetaCellOwned(pub BlockMeta); impl SimpleStorableCell for FinalBlockMetaCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_FINAL_BLOCK_META_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for FinalBlockMetaCellOwned {} #[derive(BorshSerialize)] pub struct FinalBlockMetaCellRef<'blockmeta>(pub &'blockmeta BlockMeta); impl SimpleStorableCell for FinalBlockMetaCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_FINAL_BLOCK_META_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleWritableCell for FinalBlockMetaCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize final block meta".to_owned()), ) }) } } #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct LastFinalizedBlockIdCell(pub Option); impl SimpleStorableCell for LastFinalizedBlockIdCell { type KeyParams = (); const CELL_NAME: &'static str = DB_META_LAST_FINALIZED_BLOCK_ID; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for LastFinalizedBlockIdCell {} impl SimpleWritableCell for LastFinalizedBlockIdCell { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize last finalized block id".to_owned()), ) }) } } #[derive(BorshDeserialize)] pub struct LatestBlockMetaCellOwned(pub BlockMeta); impl SimpleStorableCell for LatestBlockMetaCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_META_LATEST_BLOCK_META_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for LatestBlockMetaCellOwned {} #[derive(BorshSerialize)] pub struct LatestBlockMetaCellRef<'blockmeta>(pub &'blockmeta BlockMeta); impl SimpleStorableCell for LatestBlockMetaCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_META_LATEST_BLOCK_META_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleWritableCell for LatestBlockMetaCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message(err, Some("Failed to serialize last block meta".to_owned())) }) } } /// Opaque bytes for the zone-sdk sequencer checkpoint. The caller is /// responsible for the actual encoding (we use `serde_json` since /// `SequencerCheckpoint` only derives serde, not borsh). #[derive(BorshDeserialize)] pub struct ZoneSdkCheckpointCellOwned(pub Vec); impl SimpleStorableCell for ZoneSdkCheckpointCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_META_ZONE_SDK_CHECKPOINT_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for ZoneSdkCheckpointCellOwned {} #[derive(BorshSerialize)] pub struct ZoneSdkCheckpointCellRef<'bytes>(pub &'bytes [u8]); impl SimpleStorableCell for ZoneSdkCheckpointCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_META_ZONE_SDK_CHECKPOINT_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleWritableCell for ZoneSdkCheckpointCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize zone-sdk checkpoint cell".to_owned()), ) }) } } /// The last channel block read back and verified from Bedrock. /// /// Holds its L1 inscription `slot` plus the block's `id`/`hash`, and serves as /// both the anchor for the startup consistency check and the resume point for /// reconstruction. `slot` is stored as a raw `u64` because the zone-sdk `Slot` /// does not derive borsh; the caller converts to/from `Slot`. #[derive(Debug, Clone, Copy, BorshSerialize, BorshDeserialize)] pub struct ZoneAnchorRecord { pub slot: u64, pub block_id: u64, pub hash: HashType, } #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct ZoneAnchorCell(pub ZoneAnchorRecord); impl SimpleStorableCell for ZoneAnchorCell { type KeyParams = (); const CELL_NAME: &'static str = DB_META_ZONE_CURSOR_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for ZoneAnchorCell {} impl SimpleWritableCell for ZoneAnchorCell { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message(err, Some("Failed to serialize zone cursor".to_owned())) }) } } /// An L1 deposit event observed but not yet seen finalized. /// /// Purely a liveness queue: whether to actually emit a mint is decided against /// chain state (the deposit-receipt PDA), and the record is dropped once its /// mint finalizes. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct PendingDepositEventRecord { pub deposit_op_id: HashType, pub source_tx_hash: HashType, pub amount: u64, pub metadata: Vec, } /// A cross-zone delivery the watcher has read off a peer block but which is not /// yet known to be irreversibly delivered. /// /// The watcher's delivery floor is durable, so once it advances past a peer /// block that block is never re-read. This record is what stands in its place: /// block production drains it every turn, and it survives a restart. Mirrors /// [`PendingDepositEventRecord`], which solves the same problem for deposits, /// and like it carries no "submitted" mark: the record is dropped when the /// delivery itself finalizes, and re-including one meanwhile is harmless /// because the inbox no-ops a replay on chain. #[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct PendingCrossZoneDispatchRecord { /// Content-addressed replay key of the delivered message, and this record's /// identity. pub message_key: [u8; 32], /// The borsh-encoded dispatch transaction, so production can re-feed it /// without re-reading the peer channel. pub transaction: Vec, /// Production attempts that ended in an execution failure. /// /// A dispatch's payload and target accounts are chosen on the peer zone and /// validated by nobody in between, so one can fail for good. A failure can /// equally be a property of the moment, so a single one is not enough to /// give up on a delivery. Once too many accumulate the record is dropped /// rather than flagged, since a delivery nothing will retry is also a /// delivery nothing would ever remove. pub failed_attempts: u32, } impl PendingCrossZoneDispatchRecord { /// A delivery the watcher has just read: never attempted. #[must_use] pub const fn recorded(message_key: [u8; 32], transaction: Vec) -> Self { Self { message_key, transaction, failed_attempts: 0, } } } #[derive(BorshDeserialize)] pub struct PendingCrossZoneDispatchesCellOwned(pub Vec); impl SimpleStorableCell for PendingCrossZoneDispatchesCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_META_PENDING_CROSS_ZONE_DISPATCHES_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for PendingCrossZoneDispatchesCellOwned {} #[derive(BorshSerialize)] pub struct PendingCrossZoneDispatchesCellRef<'records>( pub &'records [PendingCrossZoneDispatchRecord], ); impl SimpleStorableCell for PendingCrossZoneDispatchesCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_META_PENDING_CROSS_ZONE_DISPATCHES_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleWritableCell for PendingCrossZoneDispatchesCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize pending cross-zone dispatches cell".to_owned()), ) }) } } #[derive(BorshDeserialize)] pub struct PendingDepositEventsCellOwned(pub Vec); impl SimpleStorableCell for PendingDepositEventsCellOwned { type KeyParams = (); const CELL_NAME: &'static str = DB_META_PENDING_DEPOSIT_EVENTS_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleReadableCell for PendingDepositEventsCellOwned {} #[derive(BorshSerialize)] pub struct PendingDepositEventsCellRef<'records>(pub &'records [PendingDepositEventRecord]); impl SimpleStorableCell for PendingDepositEventsCellRef<'_> { type KeyParams = (); const CELL_NAME: &'static str = DB_META_PENDING_DEPOSIT_EVENTS_KEY; const CF_NAME: &'static str = CF_META_NAME; } impl SimpleWritableCell for PendingDepositEventsCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize pending deposit events cell".to_owned()), ) }) } } /// Identifies which peer channel a cross-zone watcher cursor belongs to. The /// 32-byte peer channel id doubles as the peer's zone id. pub type PeerZoneKey = [u8; 32]; /// Opaque bytes for one peer's cross-zone read cursor. As with the zone-sdk /// checkpoint, the caller owns the encoding, since the cursor type derives serde /// rather than borsh. #[derive(BorshDeserialize)] pub struct PeerFloorCellOwned(pub Vec); impl SimpleStorableCell for PeerFloorCellOwned { type KeyParams = PeerZoneKey; const CELL_NAME: &'static str = DB_META_CROSS_ZONE_PEER_FLOOR_KEY; const CF_NAME: &'static str = CF_META_NAME; /// Folds the peer zone into the key so each peer keeps its own cursor. fn key_constructor(peer_zone: Self::KeyParams) -> DbResult> { borsh::to_vec(&(Self::CELL_NAME, peer_zone)).map_err(|err| { DbError::borsh_cast_message( err, Some(format!( "Failed to serialize {:?} key params", Self::CELL_NAME )), ) }) } } impl SimpleReadableCell for PeerFloorCellOwned {} #[derive(BorshSerialize)] pub struct PeerFloorCellRef<'bytes>(pub &'bytes [u8]); impl SimpleStorableCell for PeerFloorCellRef<'_> { type KeyParams = PeerZoneKey; const CELL_NAME: &'static str = DB_META_CROSS_ZONE_PEER_FLOOR_KEY; const CF_NAME: &'static str = CF_META_NAME; /// Folds the peer zone into the key so each peer keeps its own cursor. fn key_constructor(peer_zone: Self::KeyParams) -> DbResult> { borsh::to_vec(&(Self::CELL_NAME, peer_zone)).map_err(|err| { DbError::borsh_cast_message( err, Some(format!( "Failed to serialize {:?} key params", Self::CELL_NAME )), ) }) } } impl SimpleWritableCell for PeerFloorCellRef<'_> { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize cross-zone peer floor cell".to_owned()), ) }) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct WithdrawalReconciliationKey { pub amount: u64, pub bedrock_account_pk: [u8; 32], } #[derive(Debug, BorshSerialize, BorshDeserialize)] pub struct UnseenWithdrawCountCell(pub u64); impl SimpleStorableCell for UnseenWithdrawCountCell { type KeyParams = WithdrawalReconciliationKey; const CELL_NAME: &'static str = DB_META_UNSEEN_WITHDRAW_COUNT_KEY; const CF_NAME: &'static str = CF_META_NAME; fn key_constructor(key_params: Self::KeyParams) -> DbResult> { let WithdrawalReconciliationKey { amount, bedrock_account_pk, } = key_params; borsh::to_vec(&(Self::CELL_NAME, amount, bedrock_account_pk)).map_err(|err| { DbError::borsh_cast_message( err, Some(format!( "Failed to serialize {:?} key params", Self::CELL_NAME )), ) }) } } impl SimpleReadableCell for UnseenWithdrawCountCell {} impl SimpleWritableCell for UnseenWithdrawCountCell { fn value_constructor(&self) -> DbResult> { borsh::to_vec(&self).map_err(|err| { DbError::borsh_cast_message( err, Some("Failed to serialize unseen withdraw count".to_owned()), ) }) } } #[cfg(test)] mod uniform_tests { use crate::{ cells::SimpleStorableCell as _, sequencer::sequencer_cells::{ LEEStateCellOwned, LEEStateCellRef, LatestBlockMetaCellOwned, LatestBlockMetaCellRef, PendingDepositEventsCellOwned, PendingDepositEventsCellRef, }, }; #[test] fn state_ref_and_owned_is_aligned() { assert_eq!(LEEStateCellRef::CELL_NAME, LEEStateCellOwned::CELL_NAME); assert_eq!(LEEStateCellRef::CF_NAME, LEEStateCellOwned::CF_NAME); assert_eq!( LEEStateCellRef::key_constructor(()).unwrap(), LEEStateCellOwned::key_constructor(()).unwrap() ); } #[test] fn block_meta_ref_and_owned_is_aligned() { assert_eq!( LatestBlockMetaCellRef::CELL_NAME, LatestBlockMetaCellOwned::CELL_NAME ); assert_eq!( LatestBlockMetaCellRef::CF_NAME, LatestBlockMetaCellOwned::CF_NAME ); assert_eq!( LatestBlockMetaCellRef::key_constructor(()).unwrap(), LatestBlockMetaCellOwned::key_constructor(()).unwrap() ); } #[test] fn pending_deposit_events_ref_and_owned_is_aligned() { assert_eq!( PendingDepositEventsCellRef::CELL_NAME, PendingDepositEventsCellOwned::CELL_NAME ); assert_eq!( PendingDepositEventsCellRef::CF_NAME, PendingDepositEventsCellOwned::CF_NAME ); assert_eq!( PendingDepositEventsCellRef::key_constructor(()).unwrap(), PendingDepositEventsCellOwned::key_constructor(()).unwrap() ); } }