use alloy::primitives::{Address, B256};
use alloy::providers::Provider;
use decdn_common::admin::BindingStatus;
use decdn_common::redact::sanitize_error_sources;
use decdn_incentive::capacity_bond::CapacityBond;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct BindingReport {
pub status: BindingStatus,
pub bound_node_id: Option<[u8; 32]>,
}
impl BindingReport {
#[must_use]
pub const fn unknown() -> Self {
Self {
status: BindingStatus::Unknown,
bound_node_id: None,
}
}
}
#[must_use]
pub fn classify(bound: B256, local: B256) -> BindingReport {
if bound == B256::ZERO {
return BindingReport {
status: BindingStatus::Unbound,
bound_node_id: None,
};
}
let status = if bound == local {
BindingStatus::Bound
} else {
BindingStatus::Mismatch
};
BindingReport {
status,
bound_node_id: Some(bound.into()),
}
}
pub async fn check<P: Provider>(
provider: P,
capacity_bond: Address,
operator: Address,
local_node_id: [u8; 32],
) -> BindingReport {
let local = B256::from(local_node_id);
let bound = match CapacityBond::new(capacity_bond, provider)
.addressToNodeId(operator)
.call()
.await
{
Ok(bound) => bound,
Err(err) => {
tracing::warn!(
error = %sanitize_error_sources(&err),
%operator,
%capacity_bond,
"could not read the on-chain node-id binding at startup; slashability of this \
node's key is UNVERIFIED. Check it with `decdn node health`."
);
return BindingReport::unknown();
}
};
let report = classify(bound, local);
log(report.status, local, bound, operator);
report
}
fn log(status: BindingStatus, local: B256, bound: B256, operator: Address) {
match status {
BindingStatus::Bound => tracing::info!(
node_id = %local,
%operator,
"local node key matches the on-chain binding"
),
BindingStatus::Mismatch => tracing::warn!(
local_node_id = %local,
bound_node_id = %bound,
%operator,
"this node is serving under a key that is NOT the one bound on-chain, so it cannot be \
slashed and peers will not route to it. Either bind the local key with `decdn node \
rotate-key --key iroh --bind-existing`, or restore the key the binding names."
),
BindingStatus::Unbound => tracing::warn!(
local_node_id = %local,
%operator,
"this operator address has no on-chain node-id binding, so this node cannot be \
slashed and peers will not route to it. Register it with `decdn node register` \
(after `decdn node bond`)."
),
BindingStatus::Unknown => {}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests;