522 lines
17 KiB
Rust
Raw Normal View History

2026-03-31 09:08:40 +03:00
use borsh::{BorshDeserialize, BorshSerialize};
use common::{HashType, block::BlockMeta};
use lee::V03State;
2026-03-31 09:08:40 +03:00
use crate::{
CF_META_NAME, DbResult,
2026-04-02 17:45:49 +03:00
cells::{SimpleReadableCell, SimpleStorableCell, SimpleWritableCell},
2026-03-31 09:08:40 +03:00
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,
2026-03-31 09:08:40 +03:00
},
};
#[derive(BorshDeserialize)]
pub struct LEEStateCellOwned(pub V03State);
2026-03-31 09:08:40 +03:00
impl SimpleStorableCell for LEEStateCellOwned {
2026-03-31 09:08:40 +03:00
type KeyParams = ();
const CELL_NAME: &'static str = DB_LEE_STATE_KEY;
const CF_NAME: &'static str = CF_LEE_STATE_NAME;
2026-03-31 09:08:40 +03:00
}
impl SimpleReadableCell for LEEStateCellOwned {}
2026-03-31 09:08:40 +03:00
#[derive(BorshSerialize)]
pub struct LEEStateCellRef<'state>(pub &'state V03State);
2026-03-31 09:08:40 +03:00
impl SimpleStorableCell for LEEStateCellRef<'_> {
2026-03-31 09:08:40 +03:00
type KeyParams = ();
const CELL_NAME: &'static str = DB_LEE_STATE_KEY;
const CF_NAME: &'static str = CF_LEE_STATE_NAME;
2026-03-31 09:08:40 +03:00
}
impl SimpleWritableCell for LEEStateCellRef<'_> {
2026-03-31 09:08:40 +03:00
fn value_constructor(&self) -> DbResult<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
borsh::to_vec(&self).map_err(|err| {
DbError::borsh_cast_message(
err,
Some("Failed to serialize final block meta".to_owned()),
)
})
}
}
2026-03-31 09:08:40 +03:00
#[derive(Debug, BorshSerialize, BorshDeserialize)]
pub struct LastFinalizedBlockIdCell(pub Option<u64>);
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<Vec<u8>> {
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<Vec<u8>> {
borsh::to_vec(&self).map_err(|err| {
DbError::borsh_cast_message(err, Some("Failed to serialize last block meta".to_owned()))
})
}
}
2026-04-01 08:53:45 +03:00
2026-04-29 14:05:23 +02:00
/// Opaque bytes for the zone-sdk sequencer checkpoint. The caller is
2026-04-29 14:38:20 +02:00
/// responsible for the actual encoding (we use `serde_json` since
2026-04-29 14:05:23 +02:00
/// `SequencerCheckpoint` only derives serde, not borsh).
#[derive(BorshDeserialize)]
pub struct ZoneSdkCheckpointCellOwned(pub Vec<u8>);
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<Vec<u8>> {
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<Vec<u8>> {
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<u8>,
}
/// 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<u8>,
/// 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<u8>) -> Self {
Self {
message_key,
transaction,
failed_attempts: 0,
}
}
}
#[derive(BorshDeserialize)]
pub struct PendingCrossZoneDispatchesCellOwned(pub Vec<PendingCrossZoneDispatchRecord>);
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<Vec<u8>> {
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<PendingDepositEventRecord>);
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<Vec<u8>> {
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<u8>);
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<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
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<Vec<u8>> {
borsh::to_vec(&self).map_err(|err| {
DbError::borsh_cast_message(
err,
Some("Failed to serialize unseen withdraw count".to_owned()),
)
})
}
}
2026-04-01 08:53:45 +03:00
#[cfg(test)]
mod uniform_tests {
use crate::{
2026-04-02 17:45:49 +03:00
cells::SimpleStorableCell as _,
2026-04-01 08:53:45 +03:00
sequencer::sequencer_cells::{
LEEStateCellOwned, LEEStateCellRef, LatestBlockMetaCellOwned, LatestBlockMetaCellRef,
PendingDepositEventsCellOwned, PendingDepositEventsCellRef,
2026-04-01 08:53:45 +03:00
},
};
#[test]
fn state_ref_and_owned_is_aligned() {
assert_eq!(LEEStateCellRef::CELL_NAME, LEEStateCellOwned::CELL_NAME);
assert_eq!(LEEStateCellRef::CF_NAME, LEEStateCellOwned::CF_NAME);
2026-04-01 08:53:45 +03:00
assert_eq!(
LEEStateCellRef::key_constructor(()).unwrap(),
LEEStateCellOwned::key_constructor(()).unwrap()
2026-04-01 08:53:45 +03:00
);
}
#[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()
);
}
2026-04-01 08:53:45 +03:00
}