Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target
.DS_Store
2 changes: 1 addition & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1 @@
STRATA_FULLNODE="https://rpc.testnet-staging.stratabtc.org/"
STRATA_FULLNODE="https://strata-staging.testnet-v2.alpenlabs.io/"
1 change: 1 addition & 0 deletions backend/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions backend/bin/checkpoint-explorer/src/services/checkpoint_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,15 @@ async fn update_checkpoints_status(
status: RpcCheckpointConfStatus,
) -> anyhow::Result<()> {
let checkpoint_db = CheckpointService::new(&database.db);
let chain_status = fetcher.get_chain_status().await?;
// Highest checkpoint index that can have transitioned out of this local status.
// Pending checkpoints can become confirmed/finalized through the confirmed boundary;
// confirmed checkpoints can become finalized through the finalized boundary.
let transition_boundary_idx = match status {
RpcCheckpointConfStatus::Pending => chain_status.confirmed.epoch(),
RpcCheckpointConfStatus::Confirmed => chain_status.finalized.epoch(),
RpcCheckpointConfStatus::Finalized => return Ok(()),
};

let mut idx: u32 = match status {
RpcCheckpointConfStatus::Pending => {
Expand All @@ -208,7 +217,7 @@ async fn update_checkpoints_status(
RpcCheckpointConfStatus::Finalized => return Ok(()),
};

loop {
while idx <= transition_boundary_idx {
// This is the stopping condition for the loop. If the checkpoint is not found in the database,
// break the loop as we have already updated all the checkpoints.
let Some(checkpoint_in_db) = checkpoint_db.get_checkpoint_by_idx(idx).await else {
Expand All @@ -222,10 +231,16 @@ async fn update_checkpoints_status(
};

let new_status = checkpoint_from_rpc.status();
let new_txid = checkpoint_from_rpc.checkpoint_txid();
let current_txid = checkpoint_in_db
.l1_reference
.as_ref()
.map(|l1_ref| &l1_ref.txid);

// if there is no change in status, return by doing nothing
if checkpoint_in_db.confirmation_status == Some(new_status) {
return Ok(());
// This checkpoint is already current, but later rows in the bounded range may have changed.
if checkpoint_in_db.confirmation_status == Some(new_status) && current_txid == new_txid {
idx = idx.saturating_add(1);
continue;
}

info!(idx, %new_status, "Updating checkpoint status");
Expand All @@ -241,4 +256,6 @@ async fn update_checkpoints_status(

idx = idx.saturating_add(1);
}

Ok(())
}
3 changes: 3 additions & 0 deletions backend/model/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ serde = { workspace = true }
hex = { workspace = true }
anyhow = { workspace = true }
strata-identifiers = { workspace = true }

[dev-dependencies]
serde_json = { workspace = true }
98 changes: 98 additions & 0 deletions backend/model/src/checkpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ impl RpcCheckpointInfo {
ConfStatus::Finalized { .. } => RpcCheckpointConfStatus::Finalized,
}
}

pub fn checkpoint_txid(&self) -> Option<&Txid> {
match &self.confirmation_status {
ConfStatus::Pending => None,
ConfStatus::Confirmed { l1_reference } | ConfStatus::Finalized { l1_reference } => {
Some(&l1_reference.txid)
}
}
}
}

impl From<RpcCheckpointInfo> for ActiveModel {
Expand Down Expand Up @@ -188,3 +197,92 @@ impl From<Model> for RpcCheckpointInfoCheckpointExp {
}
}
}

#[cfg(test)]
mod tests {
use super::*;
use sea_orm::ActiveValue::Set;

fn checkpoint_json(status: serde_json::Value) -> serde_json::Value {
serde_json::json!({
"idx": 7,
"l1_range": [
{"height": 70, "blkid": "l1-start"},
{"height": 79, "blkid": "l1-end"}
],
"l2_range": [
{"slot": 700, "blkid": "l2-start"},
{"slot": 799, "blkid": "l2-end"}
],
"confirmation_status": status
})
}

fn confirmed_status(txid: &str) -> serde_json::Value {
serde_json::json!({
"status": "confirmed",
"l1_reference": {
"l1_block": {"height": 79, "blkid": "l1-end"},
"txid": txid,
"wtxid": "wtxid"
}
})
}

fn finalized_status(txid: &str) -> serde_json::Value {
serde_json::json!({
"status": "finalized",
"l1_reference": {
"l1_block": {"height": 79, "blkid": "l1-end"},
"txid": txid,
"wtxid": "wtxid"
}
})
}

#[test]
fn checkpoint_txid_is_none_for_pending() {
let checkpoint: RpcCheckpointInfo =
serde_json::from_value(checkpoint_json(serde_json::json!({"status": "pending"})))
.expect("pending checkpoint should deserialize");

assert_eq!(checkpoint.status(), RpcCheckpointConfStatus::Pending);
assert_eq!(checkpoint.checkpoint_txid(), None);
}

#[test]
fn checkpoint_txid_is_exposed_for_confirmed_and_finalized() {
let confirmed: RpcCheckpointInfo =
serde_json::from_value(checkpoint_json(confirmed_status("confirmed-txid")))
.expect("confirmed checkpoint should deserialize");
let finalized: RpcCheckpointInfo =
serde_json::from_value(checkpoint_json(finalized_status("finalized-txid")))
.expect("finalized checkpoint should deserialize");

assert_eq!(confirmed.status(), RpcCheckpointConfStatus::Confirmed);
assert_eq!(
confirmed.checkpoint_txid().map(String::as_str),
Some("confirmed-txid")
);
assert_eq!(finalized.status(), RpcCheckpointConfStatus::Finalized);
assert_eq!(
finalized.checkpoint_txid().map(String::as_str),
Some("finalized-txid")
);
}

#[test]
fn active_model_sets_checkpoint_txid_for_confirmed_checkpoint() {
let checkpoint: RpcCheckpointInfo =
serde_json::from_value(checkpoint_json(confirmed_status("confirmed-txid")))
.expect("confirmed checkpoint should deserialize");

let active_model: ActiveModel = checkpoint.into();

assert_eq!(active_model.status, Set(RpcCheckpointConfStatus::Confirmed));
assert_eq!(
active_model.checkpoint_txid,
Set(Some("confirmed-txid".to_string()))
);
}
}
3 changes: 3 additions & 0 deletions frontend/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
dist
.DS_Store
Loading