-
Notifications
You must be signed in to change notification settings - Fork 6
feat: lazy chain config init via ChainConfigWatch with epoch-boundary… #490
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: feat/tx-evaluation-rebased
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,283 @@ | ||||||||||||||||||||||||||||||||
| use std::sync::Arc; | ||||||||||||||||||||||||||||||||
| use std::time::{Duration, SystemTime, UNIX_EPOCH}; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| use bf_common::chain_config::ChainConfigCache; | ||||||||||||||||||||||||||||||||
| use bf_common::errors::BlockfrostError; | ||||||||||||||||||||||||||||||||
| use tokio::sync::watch; | ||||||||||||||||||||||||||||||||
| use tokio::time; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| use crate::chain_config::init_caches; | ||||||||||||||||||||||||||||||||
| use crate::pool::NodePool; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| const CONWAY_ERA: u16 = ChainConfigCache::CONWAY_ERA; | ||||||||||||||||||||||||||||||||
| const SYNC_THRESHOLD: f64 = 99.9; | ||||||||||||||||||||||||||||||||
| const SYNC_POLL_INTERVAL: Duration = Duration::from_secs(60); | ||||||||||||||||||||||||||||||||
| const RETRY_INTERVAL: Duration = Duration::from_secs(60); | ||||||||||||||||||||||||||||||||
| /// Buffer after computed epoch boundary before re-querying protocol params. | ||||||||||||||||||||||||||||||||
| const EPOCH_BOUNDARY_BUFFER: Duration = Duration::from_secs(30); | ||||||||||||||||||||||||||||||||
| /// Error substring returned by `sync_progress()` for non-well-known networks. | ||||||||||||||||||||||||||||||||
| const UNSUPPORTED_NETWORK_ERROR: &str = "Only well-known networks"; | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Watches the Cardano node for sync readiness and epoch-boundary protocol | ||||||||||||||||||||||||||||||||
| /// parameter changes, publishing [`ChainConfigCache`] updates via a | ||||||||||||||||||||||||||||||||
| /// `tokio::sync::watch` channel. | ||||||||||||||||||||||||||||||||
| #[derive(Clone)] | ||||||||||||||||||||||||||||||||
| pub struct ChainConfigWatch { | ||||||||||||||||||||||||||||||||
| rx: watch::Receiver<Option<Arc<ChainConfigCache>>>, | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| impl ChainConfigWatch { | ||||||||||||||||||||||||||||||||
| /// Spawn the background monitor and return immediately. | ||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||
| /// The watch value starts as `None` and is set to `Some(…)` once the node | ||||||||||||||||||||||||||||||||
| /// reaches the Conway era with sufficient sync progress. | ||||||||||||||||||||||||||||||||
| pub fn spawn(node_pool: NodePool) -> Self { | ||||||||||||||||||||||||||||||||
| let (tx, rx) = watch::channel(None); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| tokio::spawn(async move { | ||||||||||||||||||||||||||||||||
| monitor_loop(node_pool, tx).await; | ||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Self { rx } | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Returns the current config or a 503 if the node is not yet synced. | ||||||||||||||||||||||||||||||||
| pub fn get(&self) -> Result<Arc<ChainConfigCache>, BlockfrostError> { | ||||||||||||||||||||||||||||||||
| self.rx.borrow().clone().ok_or_else(|| { | ||||||||||||||||||||||||||||||||
| BlockfrostError::service_unavailable( | ||||||||||||||||||||||||||||||||
| "Chain configuration is not yet available. The Cardano node may still be syncing." | ||||||||||||||||||||||||||||||||
| .to_string(), | ||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| /// Wait until the first config is available (node synced and init complete). | ||||||||||||||||||||||||||||||||
| pub async fn wait_ready(&mut self) { | ||||||||||||||||||||||||||||||||
| while self.rx.borrow().is_none() { | ||||||||||||||||||||||||||||||||
| if self.rx.changed().await.is_err() { | ||||||||||||||||||||||||||||||||
| break; | ||||||||||||||||||||||||||||||||
|
Comment on lines
+57
to
+58
|
||||||||||||||||||||||||||||||||
| if self.rx.changed().await.is_err() { | |
| break; | |
| match self.rx.changed().await { | |
| Ok(_) => { | |
| // value changed; loop condition will re-check for readiness | |
| } | |
| Err(err) => { | |
| // Sender was dropped before config became available; this indicates | |
| // that the background task has terminated and readiness will never be reached. | |
| tracing::error!( | |
| "ChainConfigWatch: watch channel closed before configuration became available: {}", | |
| err | |
| ); | |
| break; | |
| } |
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ChainConfigCache::CONWAY_ERA is documented as the minimum supported era, but the sync gate treats any era other than exactly Conway as "not ready". This will block forever once the node moves to a future era (>6) even if the platform could still operate. Consider using a info.era < CONWAY_ERA check (or otherwise defining the intended policy explicitly).
Copilot
AI
Mar 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
duration_until_next_epoch waits a full epoch when slot_in_epoch == 0 (exactly on an epoch boundary), because slots_until_next becomes epoch_length. That can delay the epoch-boundary refresh by an entire epoch in the boundary case. Consider computing slots_until_next so that a boundary yields 0 (or 1) and then adding the buffer, e.g. (epoch_length - slot_in_epoch) % epoch_length.
| let slots_until_next = slot_config.epoch_length - slot_in_epoch; | |
| // Compute slots until next epoch boundary; if we're exactly on a boundary, | |
| // this yields 0 instead of a full epoch. | |
| let slots_until_next = | |
| (slot_config.epoch_length - slot_in_epoch) % slot_config.epoch_length; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doc comment uses an intra-doc link to
ChainConfigWatch, but that type lives inbf_nodeand isn’t in scope in thebf_commoncrate. This can produce broken intra-doc-link warnings duringcargo doc/docs.rs. Consider removing the link formatting or referencing it as plain text (or using an external URL) to avoid unresolved links.