v0.3.14-pre.008
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-worker-raw-transaction-ingest-lib/src/runtime_resources.rs
|
||||
// version: 34
|
||||
// version: 35
|
||||
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
@@ -88,6 +88,14 @@ enum RawTransactionIngestLiveSource {
|
||||
Yellowstone(crate::RawTransactionIngestYellowstoneSource),
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct RawTransactionIngestSourceRuntimeShared {
|
||||
continuity_contracts: std::sync::Arc<std::sync::Mutex<crate::RawTransactionIngestContinuityContracts>>,
|
||||
global_hydration_registry: std::sync::Arc<RawTransactionIngestGlobalHydrationRegistry>,
|
||||
hydration_in_flight_limit: usize,
|
||||
hydration_pending_limit: usize,
|
||||
}
|
||||
|
||||
struct RawTransactionIngestSourceInventory {
|
||||
source_keys: std::vec::Vec<[u8; 32]>,
|
||||
source_projections: std::vec::Vec<crate::RawTransactionIngestProcessingFrontierProjection>,
|
||||
@@ -211,56 +219,20 @@ impl RawTransactionIngestLiveSource {
|
||||
settings: crate::RawTransactionIngestSettings,
|
||||
stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
global_hydration_registry: std::sync::Arc<RawTransactionIngestGlobalHydrationRegistry>,
|
||||
hydration_pending_limit: usize,
|
||||
hydration_in_flight_limit: usize,
|
||||
inventory_publisher: RawTransactionIngestSourceInventoryPublisher,
|
||||
shared: RawTransactionIngestSourceRuntimeShared,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let (source_frontier_sender, mut source_frontier_receiver) =
|
||||
tokio::sync::watch::channel(crate::RawTransactionIngestProcessingFrontierProjection::empty());
|
||||
let mut source_future = std::boxed::Box::pin(async move {
|
||||
return match self {
|
||||
Self::HeliusTransaction(source) => {
|
||||
source
|
||||
.run(
|
||||
settings,
|
||||
stop_receiver,
|
||||
admission_sender,
|
||||
source_frontier_sender,
|
||||
global_hydration_registry,
|
||||
hydration_pending_limit,
|
||||
hydration_in_flight_limit,
|
||||
)
|
||||
.await
|
||||
Self::HeliusTransaction(source) => source.run(settings, stop_receiver, admission_sender, source_frontier_sender, shared).await,
|
||||
Self::HttpBlockPolling(source) => {
|
||||
source.run(settings, stop_receiver, admission_sender, source_frontier_sender, shared.continuity_contracts).await
|
||||
},
|
||||
Self::HttpBlockPolling(source) => source.run(settings, stop_receiver, admission_sender, source_frontier_sender).await,
|
||||
Self::StandardBlock(source) => source.run(settings, stop_receiver, admission_sender, source_frontier_sender).await,
|
||||
Self::StandardLogs(source) => {
|
||||
source
|
||||
.run(
|
||||
settings,
|
||||
stop_receiver,
|
||||
admission_sender,
|
||||
source_frontier_sender,
|
||||
global_hydration_registry,
|
||||
hydration_pending_limit,
|
||||
hydration_in_flight_limit,
|
||||
)
|
||||
.await
|
||||
},
|
||||
Self::Yellowstone(source) => {
|
||||
source
|
||||
.run(
|
||||
settings,
|
||||
stop_receiver,
|
||||
admission_sender,
|
||||
source_frontier_sender,
|
||||
global_hydration_registry,
|
||||
hydration_pending_limit,
|
||||
hydration_in_flight_limit,
|
||||
)
|
||||
.await
|
||||
},
|
||||
Self::StandardLogs(source) => source.run(settings, stop_receiver, admission_sender, source_frontier_sender, shared).await,
|
||||
Self::Yellowstone(source) => source.run(settings, stop_receiver, admission_sender, source_frontier_sender, shared).await,
|
||||
};
|
||||
});
|
||||
loop {
|
||||
@@ -389,6 +361,23 @@ impl RawTransactionIngestSourceInventory {
|
||||
return Self { source_keys, source_projections };
|
||||
}
|
||||
|
||||
fn supervisor_state(&self) -> ksp_core_lib::Result<(std::vec::Vec<[u8; 32]>, std::option::Option<u64>)> {
|
||||
if self.source_keys.len() != self.source_projections.len() {
|
||||
return std::result::Result::Err(crate::runtime_error("source.inventory_shape_mismatch"));
|
||||
}
|
||||
let mut active_source_keys = std::vec::Vec::new();
|
||||
for (source_key, projection) in self.source_keys.iter().zip(self.source_projections.iter()) {
|
||||
if projection.source_state() == std::option::Option::Some(crate::RawTransactionIngestSourceState::Active) {
|
||||
active_source_keys.push(*source_key);
|
||||
}
|
||||
}
|
||||
let aggregate = match self.aggregate() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return std::result::Result::Ok((active_source_keys, aggregate.processing_frontier_slot()));
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
entry_index: usize,
|
||||
@@ -725,6 +714,7 @@ struct RawTransactionIngestHydrationContext {
|
||||
protocol: &'static str,
|
||||
route: RawTransactionIngestSourceRoute,
|
||||
route_prefix: &'static str,
|
||||
source_key: [u8; 32],
|
||||
source_key_domain: &'static [u8],
|
||||
}
|
||||
|
||||
@@ -809,6 +799,7 @@ impl crate::RawTransactionIngestYellowstoneSource {
|
||||
protocol: RAW_TRANSACTION_INGEST_YELLOWSTONE_HTTP_PROTOCOL,
|
||||
route: self.route.clone(),
|
||||
route_prefix: "ys",
|
||||
source_key: self.source_key,
|
||||
source_key_domain: RAW_TRANSACTION_INGEST_YELLOWSTONE_HTTP_SOURCE_KEY_DOMAIN,
|
||||
};
|
||||
}
|
||||
@@ -820,10 +811,10 @@ impl crate::RawTransactionIngestYellowstoneSource {
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
|
||||
global_hydration_registry: std::sync::Arc<RawTransactionIngestGlobalHydrationRegistry>,
|
||||
hydration_pending_limit: usize,
|
||||
hydration_in_flight_limit: usize,
|
||||
shared: RawTransactionIngestSourceRuntimeShared,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let RawTransactionIngestSourceRuntimeShared { continuity_contracts, global_hydration_registry, hydration_in_flight_limit, hydration_pending_limit } =
|
||||
shared;
|
||||
let opened = tokio::select! {
|
||||
biased;
|
||||
_ = stop_receiver.changed() => {
|
||||
@@ -894,6 +885,7 @@ impl crate::RawTransactionIngestYellowstoneSource {
|
||||
&admission_sender,
|
||||
&mut stop_receiver,
|
||||
&mut processing_frontier,
|
||||
&continuity_contracts,
|
||||
)
|
||||
.await;
|
||||
match handled {
|
||||
@@ -1049,6 +1041,7 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
protocol: RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_HTTP_PROTOCOL,
|
||||
route: self.route.clone(),
|
||||
route_prefix: "hx",
|
||||
source_key: self.source_key,
|
||||
source_key_domain: RAW_TRANSACTION_INGEST_HELIUS_TRANSACTION_HTTP_SOURCE_KEY_DOMAIN,
|
||||
};
|
||||
}
|
||||
@@ -1060,10 +1053,10 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
|
||||
global_hydration_registry: std::sync::Arc<RawTransactionIngestGlobalHydrationRegistry>,
|
||||
hydration_pending_limit: usize,
|
||||
hydration_in_flight_limit: usize,
|
||||
shared: RawTransactionIngestSourceRuntimeShared,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let RawTransactionIngestSourceRuntimeShared { continuity_contracts, global_hydration_registry, hydration_in_flight_limit, hydration_pending_limit } =
|
||||
shared;
|
||||
let connected = tokio::select! {
|
||||
biased;
|
||||
_ = stop_receiver.changed() => {
|
||||
@@ -1101,7 +1094,7 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let mut fault = std::option::Option::None;
|
||||
let mut bounded_websocket_incident = false;
|
||||
let mut bounded_websocket_incident = std::option::Option::None;
|
||||
loop {
|
||||
if *stop_receiver.borrow() {
|
||||
break;
|
||||
@@ -1110,11 +1103,14 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
if bounded_websocket_incident && coordinator.pending_signal_count == 0 && coordinator.tasks.is_empty() {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.continuity_gap_proven"));
|
||||
if let std::option::Option::Some((start_slot, end_slot)) = bounded_websocket_incident
|
||||
&& coordinator.pending_signal_count == 0
|
||||
&& coordinator.tasks.is_empty()
|
||||
{
|
||||
fault = std::option::Option::Some(continuity_range_error("source.continuity_gap_proven", start_slot, end_slot));
|
||||
break;
|
||||
}
|
||||
let can_receive = coordinator.can_receive() && !bounded_websocket_incident;
|
||||
let can_receive = coordinator.can_receive() && bounded_websocket_incident.is_none();
|
||||
if !can_receive && coordinator.tasks.is_empty() {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.hydration_stalled"));
|
||||
break;
|
||||
@@ -1153,6 +1149,7 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
&admission_sender,
|
||||
&mut stop_receiver,
|
||||
&mut processing_frontier,
|
||||
&continuity_contracts,
|
||||
)
|
||||
.await;
|
||||
match handled {
|
||||
@@ -1212,7 +1209,9 @@ impl crate::RawTransactionIngestHeliusTransactionSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
bounded_websocket_incident = bounded_websocket_incident || incident_bounded;
|
||||
if bounded_websocket_incident.is_none() {
|
||||
bounded_websocket_incident = incident_bounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1333,6 +1332,7 @@ impl crate::RawTransactionIngestHttpBlockPollingSource {
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
|
||||
continuity_contracts: std::sync::Arc<std::sync::Mutex<crate::RawTransactionIngestContinuityContracts>>,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let context_config = ksp_onchain_transport_lib::SolanaContextConfig::new(std::option::Option::Some(self.commitment), std::option::Option::None);
|
||||
let get_block_config = ksp_onchain_transport_lib::SolanaGetBlockConfig::new(
|
||||
@@ -1390,6 +1390,7 @@ impl crate::RawTransactionIngestHttpBlockPollingSource {
|
||||
std::option::Option::None => u64::MAX,
|
||||
};
|
||||
let window_end = current_tip.min(candidate_end);
|
||||
let window_start = next_scan_slot;
|
||||
let discovered = discover_http_block_window(
|
||||
&self.http_pool,
|
||||
&self.polling_role,
|
||||
@@ -1469,6 +1470,17 @@ impl crate::RawTransactionIngestHttpBlockPollingSource {
|
||||
}
|
||||
}
|
||||
if !blocked_by_null && let std::option::Option::Some(proven_end_slot) = discovery.proven_end_slot {
|
||||
let coverage_result = {
|
||||
let mut contracts = match continuity_contracts.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
contracts.record_coverage_epoch(self.source_key, window_start, proven_end_slot)
|
||||
};
|
||||
if let std::result::Result::Err(error) = coverage_result {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
next_scan_slot = match proven_end_slot.checked_add(1) {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
@@ -1680,8 +1692,8 @@ impl crate::RawTransactionIngestStandardBlockSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
if incident_bounded {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.continuity_gap_proven"));
|
||||
if let std::option::Option::Some((start_slot, end_slot)) = incident_bounded {
|
||||
fault = std::option::Option::Some(continuity_range_error("source.continuity_gap_proven", start_slot, end_slot));
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
@@ -1706,8 +1718,8 @@ impl crate::RawTransactionIngestStandardBlockSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
if incident_bounded {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.continuity_gap_proven"));
|
||||
if let std::option::Option::Some((start_slot, end_slot)) = incident_bounded {
|
||||
fault = std::option::Option::Some(continuity_range_error("source.continuity_gap_proven", start_slot, end_slot));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1834,6 +1846,7 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
protocol: RAW_TRANSACTION_INGEST_STANDARD_LOGS_HTTP_PROTOCOL,
|
||||
route: self.route.clone(),
|
||||
route_prefix: "ws",
|
||||
source_key: self.source_key,
|
||||
source_key_domain: RAW_TRANSACTION_INGEST_STANDARD_LOGS_HTTP_SOURCE_KEY_DOMAIN,
|
||||
};
|
||||
}
|
||||
@@ -1845,10 +1858,10 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
admission_sender: tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
processing_frontier_sender: tokio::sync::watch::Sender<crate::RawTransactionIngestProcessingFrontierProjection>,
|
||||
global_hydration_registry: std::sync::Arc<RawTransactionIngestGlobalHydrationRegistry>,
|
||||
hydration_pending_limit: usize,
|
||||
hydration_in_flight_limit: usize,
|
||||
shared: RawTransactionIngestSourceRuntimeShared,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
let RawTransactionIngestSourceRuntimeShared { continuity_contracts, global_hydration_registry, hydration_in_flight_limit, hydration_pending_limit } =
|
||||
shared;
|
||||
let connected = tokio::select! {
|
||||
biased;
|
||||
_ = stop_receiver.changed() => {
|
||||
@@ -1886,7 +1899,7 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let mut fault = std::option::Option::None;
|
||||
let mut bounded_websocket_incident = false;
|
||||
let mut bounded_websocket_incident = std::option::Option::None;
|
||||
loop {
|
||||
if *stop_receiver.borrow() {
|
||||
break;
|
||||
@@ -1895,11 +1908,14 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
if bounded_websocket_incident && coordinator.pending_signal_count == 0 && coordinator.tasks.is_empty() {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.continuity_gap_proven"));
|
||||
if let std::option::Option::Some((start_slot, end_slot)) = bounded_websocket_incident
|
||||
&& coordinator.pending_signal_count == 0
|
||||
&& coordinator.tasks.is_empty()
|
||||
{
|
||||
fault = std::option::Option::Some(continuity_range_error("source.continuity_gap_proven", start_slot, end_slot));
|
||||
break;
|
||||
}
|
||||
let can_receive = coordinator.can_receive() && !bounded_websocket_incident;
|
||||
let can_receive = coordinator.can_receive() && bounded_websocket_incident.is_none();
|
||||
if !can_receive && coordinator.tasks.is_empty() {
|
||||
fault = std::option::Option::Some(crate::runtime_error("source.hydration_stalled"));
|
||||
break;
|
||||
@@ -1938,6 +1954,7 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
&admission_sender,
|
||||
&mut stop_receiver,
|
||||
&mut processing_frontier,
|
||||
&continuity_contracts,
|
||||
)
|
||||
.await;
|
||||
match handled {
|
||||
@@ -1990,7 +2007,9 @@ impl crate::RawTransactionIngestStandardLogsSource {
|
||||
fault = std::option::Option::Some(error);
|
||||
break;
|
||||
}
|
||||
bounded_websocket_incident = bounded_websocket_incident || incident_bounded;
|
||||
if bounded_websocket_incident.is_none() {
|
||||
bounded_websocket_incident = incident_bounded;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2233,15 +2252,15 @@ impl crate::RawTransactionIngestRuntimeResources {
|
||||
if self.sources.is_empty() || self.sources.len() > crate::MAX_RAW_TRANSACTION_INGEST_LIVE_SOURCES {
|
||||
return std::result::Result::Err(crate::runtime_error("runtime_resources.source_collection_invalid"));
|
||||
}
|
||||
let mut repair_capabilities = std::vec::Vec::with_capacity(self.sources.len());
|
||||
let mut continuity_capabilities = std::vec::Vec::with_capacity(self.sources.len());
|
||||
for source in &self.sources {
|
||||
let capability = match source.repair_capability_descriptor() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
repair_capabilities.push(capability);
|
||||
continuity_capabilities.push(capability);
|
||||
}
|
||||
let continuity_contracts = match crate::RawTransactionIngestContinuityContracts::new(repair_capabilities) {
|
||||
let continuity_contracts = match crate::RawTransactionIngestContinuityContracts::new(continuity_capabilities) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
@@ -2249,6 +2268,7 @@ impl crate::RawTransactionIngestRuntimeResources {
|
||||
if let std::result::Result::Err(error) = continuity_contracts.validate_for_source_count(continuity_source_count) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let continuity_contracts = std::sync::Arc::new(std::sync::Mutex::new(continuity_contracts));
|
||||
let source_keys = self.sources.iter().map(RawTransactionIngestLiveSource::source_key).collect::<std::vec::Vec<_>>();
|
||||
let hydration_source_count = self.sources.iter().filter(|source| return source.uses_hydration()).count();
|
||||
if let std::result::Result::Err(error) =
|
||||
@@ -2282,24 +2302,35 @@ impl crate::RawTransactionIngestRuntimeResources {
|
||||
let source_admission_sender = admission_sender.clone();
|
||||
let source_settings = settings.clone();
|
||||
let source_stop_receiver = source_stop_receiver.clone();
|
||||
let source_global_hydration_registry = std::sync::Arc::clone(&global_hydration_registry);
|
||||
let source_shared = RawTransactionIngestSourceRuntimeShared {
|
||||
continuity_contracts: std::sync::Arc::clone(&continuity_contracts),
|
||||
global_hydration_registry: std::sync::Arc::clone(&global_hydration_registry),
|
||||
hydration_in_flight_limit,
|
||||
hydration_pending_limit,
|
||||
};
|
||||
let source_key = source.source_key();
|
||||
let _abort_handle = children.spawn(async move {
|
||||
return source
|
||||
.run(
|
||||
source_settings,
|
||||
source_stop_receiver,
|
||||
source_admission_sender,
|
||||
source_global_hydration_registry,
|
||||
hydration_pending_limit,
|
||||
hydration_in_flight_limit,
|
||||
publisher,
|
||||
)
|
||||
.await;
|
||||
let result = source.run(source_settings, source_stop_receiver, source_admission_sender, publisher, source_shared).await;
|
||||
return (source_key, result);
|
||||
});
|
||||
}
|
||||
std::mem::drop(admission_sender);
|
||||
let result = supervise_live_source_tasks(stop_receiver, source_stop_sender, children).await;
|
||||
if let std::result::Result::Err(error) = continuity_contracts.validate_for_source_count(continuity_source_count) {
|
||||
let result = supervise_live_source_tasks(
|
||||
stop_receiver,
|
||||
source_stop_sender,
|
||||
children,
|
||||
std::sync::Arc::clone(&continuity_contracts),
|
||||
std::sync::Arc::clone(&inventory),
|
||||
)
|
||||
.await;
|
||||
let validation = {
|
||||
let contracts = match continuity_contracts.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
contracts.validate_for_source_count(continuity_source_count)
|
||||
};
|
||||
if let std::result::Result::Err(error) = validation {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
return result;
|
||||
@@ -2384,7 +2415,9 @@ fn source_projection_with_state(
|
||||
async fn supervise_live_source_tasks(
|
||||
mut stop_receiver: tokio::sync::watch::Receiver<bool>,
|
||||
source_stop_sender: tokio::sync::watch::Sender<bool>,
|
||||
mut children: tokio::task::JoinSet<ksp_core_lib::Result<()>>,
|
||||
mut children: tokio::task::JoinSet<([u8; 32], ksp_core_lib::Result<()>)>,
|
||||
continuity_contracts: std::sync::Arc<std::sync::Mutex<crate::RawTransactionIngestContinuityContracts>>,
|
||||
inventory: std::sync::Arc<std::sync::Mutex<RawTransactionIngestSourceInventory>>,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
loop {
|
||||
if *stop_receiver.borrow() {
|
||||
@@ -2402,21 +2435,131 @@ async fn supervise_live_source_tasks(
|
||||
}
|
||||
value = children.join_next(), if !children.is_empty() => value,
|
||||
};
|
||||
let first_fault = match joined {
|
||||
std::option::Option::Some(std::result::Result::Ok(std::result::Result::Ok(()))) => {
|
||||
std::option::Option::Some(crate::runtime_error("source.configured_source_closed"))
|
||||
let (source_key, source_result) = match joined {
|
||||
std::option::Option::Some(std::result::Result::Ok(value)) => value,
|
||||
std::option::Option::Some(std::result::Result::Err(_)) => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(crate::runtime_error("source.task_join_failed"))).await;
|
||||
},
|
||||
std::option::Option::None => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(crate::runtime_error("source.task_set_empty"))).await;
|
||||
},
|
||||
std::option::Option::Some(std::result::Result::Ok(std::result::Result::Err(error))) => std::option::Option::Some(error),
|
||||
std::option::Option::Some(std::result::Result::Err(_)) => std::option::Option::Some(crate::runtime_error("source.task_join_failed")),
|
||||
std::option::Option::None => std::option::Option::Some(crate::runtime_error("source.task_set_empty")),
|
||||
};
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, first_fault).await;
|
||||
let source_fault = match source_result {
|
||||
std::result::Result::Ok(()) => crate::runtime_error("source.configured_source_closed"),
|
||||
std::result::Result::Err(error) => error,
|
||||
};
|
||||
if !source_loss_is_reconcilable(&source_fault) {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(source_fault)).await;
|
||||
}
|
||||
let (active_source_keys, processing_frontier_slot) = {
|
||||
let inventory = match inventory.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
match inventory.supervisor_state() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(error)).await;
|
||||
},
|
||||
}
|
||||
};
|
||||
let continuity_range = match source_loss_continuity_range(&source_fault) {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(error)).await;
|
||||
},
|
||||
};
|
||||
let continuity_range = match continuity_range {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(source_fault)).await;
|
||||
},
|
||||
};
|
||||
let decision = {
|
||||
let mut contracts = match continuity_contracts.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
if let std::result::Result::Err(error) = contracts.record_source_loss_gap(source_key, continuity_range.0, continuity_range.1) {
|
||||
std::result::Result::Err(error)
|
||||
} else {
|
||||
contracts.source_loss_decision(source_key, active_source_keys.as_slice(), processing_frontier_slot)
|
||||
}
|
||||
};
|
||||
let decision = match decision {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(error)).await;
|
||||
},
|
||||
};
|
||||
match decision {
|
||||
crate::RawTransactionIngestSourceLossDecision::Continue => {
|
||||
continue;
|
||||
},
|
||||
crate::RawTransactionIngestSourceLossDecision::Fault => {
|
||||
source_stop_sender.send_replace(true);
|
||||
return drain_live_source_tasks(&mut children, std::option::Option::Some(source_fault)).await;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn continuity_range_error(condition: &'static str, start_slot: u64, end_slot: u64) -> ksp_core_lib::Error {
|
||||
return crate::runtime_error(condition)
|
||||
.with_context("continuity_start_slot", start_slot.to_string())
|
||||
.with_context("continuity_end_slot", end_slot.to_string());
|
||||
}
|
||||
|
||||
fn source_loss_continuity_range(error: &ksp_core_lib::Error) -> ksp_core_lib::Result<std::option::Option<(u64, u64)>> {
|
||||
let mut start_slot = std::option::Option::None;
|
||||
let mut end_slot = std::option::Option::None;
|
||||
for context in error.context() {
|
||||
if context.key() == "continuity_start_slot" {
|
||||
start_slot = match context.value().parse::<u64>() {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("continuity.source_loss_range_invalid")),
|
||||
};
|
||||
} else if context.key() == "continuity_end_slot" {
|
||||
end_slot = match context.value().parse::<u64>() {
|
||||
std::result::Result::Ok(value) => std::option::Option::Some(value),
|
||||
std::result::Result::Err(_) => return std::result::Result::Err(crate::runtime_error("continuity.source_loss_range_invalid")),
|
||||
};
|
||||
}
|
||||
}
|
||||
return match (start_slot, end_slot) {
|
||||
(std::option::Option::None, std::option::Option::None) => std::result::Result::Ok(std::option::Option::None),
|
||||
(std::option::Option::Some(start), std::option::Option::Some(end)) if end >= start => std::result::Result::Ok(std::option::Option::Some((start, end))),
|
||||
_ => std::result::Result::Err(crate::runtime_error("continuity.source_loss_range_invalid")),
|
||||
};
|
||||
}
|
||||
|
||||
fn source_loss_is_reconcilable(error: &ksp_core_lib::Error) -> bool {
|
||||
if error.code() == crate::ERROR_CODE_RAW_TRANSACTION_INGEST_SOURCE_FAILED {
|
||||
return true;
|
||||
}
|
||||
if error.code() != crate::ERROR_CODE_RAW_TRANSACTION_INGEST_RUNTIME_INVALID {
|
||||
return false;
|
||||
}
|
||||
return error.context().iter().any(|context| {
|
||||
if context.key() != "condition" {
|
||||
return false;
|
||||
}
|
||||
return matches!(
|
||||
context.value(),
|
||||
"source.configured_source_closed" | "source.continuity_gap_proven" | "source.replay_coverage_unproven" | "source.websocket_incident_unbounded"
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
async fn drain_live_source_tasks(
|
||||
children: &mut tokio::task::JoinSet<ksp_core_lib::Result<()>>,
|
||||
children: &mut tokio::task::JoinSet<([u8; 32], ksp_core_lib::Result<()>)>,
|
||||
mut first_fault: std::option::Option<ksp_core_lib::Error>,
|
||||
) -> ksp_core_lib::Result<()> {
|
||||
while let std::option::Option::Some(joined) = children.join_next().await {
|
||||
@@ -2424,8 +2567,8 @@ async fn drain_live_source_tasks(
|
||||
continue;
|
||||
}
|
||||
first_fault = match joined {
|
||||
std::result::Result::Ok(std::result::Result::Ok(())) => std::option::Option::None,
|
||||
std::result::Result::Ok(std::result::Result::Err(error)) => std::option::Option::Some(error),
|
||||
std::result::Result::Ok((_source_key, std::result::Result::Ok(()))) => std::option::Option::None,
|
||||
std::result::Result::Ok((_source_key, std::result::Result::Err(error))) => std::option::Option::Some(error),
|
||||
std::result::Result::Err(_) => std::option::Option::Some(crate::runtime_error("source.task_join_failed")),
|
||||
};
|
||||
}
|
||||
@@ -3930,19 +4073,20 @@ impl RawTransactionIngestProcessingFrontierReporter {
|
||||
return std::result::Result::Ok(());
|
||||
}
|
||||
|
||||
fn observe_websocket_post_incident_slot(&mut self, slot: u64) -> ksp_core_lib::Result<bool> {
|
||||
fn observe_websocket_post_incident_slot(&mut self, slot: u64) -> ksp_core_lib::Result<std::option::Option<(u64, u64)>> {
|
||||
let anchor = match self.websocket_incident_anchor.as_mut() {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => return std::result::Result::Ok(false),
|
||||
std::option::Option::None => return std::result::Result::Ok(std::option::Option::None),
|
||||
};
|
||||
if anchor.end_slot().is_some() {
|
||||
return std::result::Result::Ok(false);
|
||||
return std::result::Result::Ok(std::option::Option::None);
|
||||
}
|
||||
if let std::result::Result::Err(error) = anchor.close_at(slot) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let range = (anchor.start_slot(), slot);
|
||||
self.publish();
|
||||
return std::result::Result::Ok(true);
|
||||
return std::result::Result::Ok(std::option::Option::Some(range));
|
||||
}
|
||||
|
||||
fn observe_source_continuity(
|
||||
@@ -4217,6 +4361,7 @@ impl RawTransactionIngestHydrationCoordinator {
|
||||
admission_sender: &tokio::sync::mpsc::Sender<crate::RawTransactionIngress>,
|
||||
stop_receiver: &mut tokio::sync::watch::Receiver<bool>,
|
||||
processing_frontier: &mut RawTransactionIngestProcessingFrontierReporter,
|
||||
continuity_contracts: &std::sync::Arc<std::sync::Mutex<crate::RawTransactionIngestContinuityContracts>>,
|
||||
) -> ksp_core_lib::Result<bool> {
|
||||
let fetched = match joined {
|
||||
std::result::Result::Ok(std::result::Result::Ok(value)) => value,
|
||||
@@ -4251,6 +4396,16 @@ impl RawTransactionIngestHydrationCoordinator {
|
||||
RawTransactionIngestKnownReferenceMissingDisposition::AwaitCoverage
|
||||
| RawTransactionIngestKnownReferenceMissingDisposition::PreferBlockSlot => {},
|
||||
}
|
||||
let continuity_result = {
|
||||
let mut contracts = match continuity_contracts.lock() {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(poisoned) => poisoned.into_inner(),
|
||||
};
|
||||
contracts.record_known_reference_gap(hydration.source_key, signal_slot)
|
||||
};
|
||||
if let std::result::Result::Err(error) = continuity_result {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if let std::result::Result::Err(error) = processing_frontier.settle_pending(signal_slot) {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user