Skip to content

Commit 35a8bd6

Browse files
afckclaude
andcommitted
Add NodeError::is_expected() and WARN logging for unexpected validator errors
Add an `is_expected()` method to `NodeError` that classifies errors as normal protocol flow (BlobsNotFound, EventsNotFound, WrongRound, etc.) vs unexpected issues (network errors, validator misbehavior). This mirrors the `is_local()` pattern used on the server side. Use this in the updater's error escape paths (send_confirmed_certificate, send_validated_certificate, send_block_proposal, request_timeout) to log unexpected errors at WARN while keeping expected ones at DEBUG. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 986daa2 commit 35a8bd6

File tree

2 files changed

+65
-4
lines changed

2 files changed

+65
-4
lines changed

linera-core/src/node.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,28 @@ pub enum NodeError {
340340
},
341341
}
342342

343+
impl NodeError {
344+
/// Returns whether this error is an expected part of the protocol flow.
345+
///
346+
/// Expected errors are those that validators return during normal operation and that
347+
/// the client handles automatically (e.g. by supplying missing data and retrying).
348+
/// Unexpected errors indicate genuine network issues, validator misbehavior, or
349+
/// internal problems.
350+
pub fn is_expected(&self) -> bool {
351+
matches!(
352+
self,
353+
NodeError::BlobsNotFound(_)
354+
| NodeError::EventsNotFound(_)
355+
| NodeError::MissingCrossChainUpdate { .. }
356+
| NodeError::WrongRound(_)
357+
| NodeError::UnexpectedBlockHeight { .. }
358+
| NodeError::InactiveChain(_)
359+
| NodeError::InvalidTimestamp { .. }
360+
| NodeError::MissingCertificateValue
361+
)
362+
}
363+
}
364+
343365
impl From<tonic::Status> for NodeError {
344366
fn from(status: tonic::Status) -> Self {
345367
Self::GrpcError {

linera-core/src/updater.rs

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,18 @@ where
280280
self.remote_node.node.upload_blobs(blobs).await?;
281281
sent_blobs = true;
282282
}
283-
result => return Ok(result?),
283+
result => {
284+
if let Err(err) = &result {
285+
if !err.is_expected() {
286+
tracing::warn!(
287+
remote_node = self.remote_node.address(),
288+
%err,
289+
"unexpected error from validator",
290+
);
291+
}
292+
}
293+
return Ok(result?);
294+
}
284295
}
285296
result = self
286297
.remote_node
@@ -325,10 +336,20 @@ where
325336
}
326337
_ => return Ok(result?),
327338
}
328-
Ok(self
339+
let result = self
329340
.remote_node
330341
.handle_validated_certificate(certificate)
331-
.await?)
342+
.await;
343+
if let Err(err) = &result {
344+
if !err.is_expected() {
345+
tracing::warn!(
346+
remote_node = self.remote_node.address(),
347+
%err,
348+
"unexpected error from validator",
349+
);
350+
}
351+
}
352+
Ok(result?)
332353
}
333354

334355
/// Requests a vote for a timeout certificate for the given round from the remote node.
@@ -348,6 +369,14 @@ where
348369
.await;
349370
if let Err(err) = &result {
350371
self.sync_if_needed(chain_id, round, height, err).await?;
372+
if !err.is_expected() {
373+
tracing::warn!(
374+
remote_node = self.remote_node.address(),
375+
%chain_id,
376+
%err,
377+
"unexpected error from validator",
378+
);
379+
}
351380
}
352381
Ok(result?)
353382
}
@@ -606,7 +635,17 @@ where
606635
.await;
607636
}
608637
// Fail immediately on other errors.
609-
Err(err) => return Err(err.into()),
638+
Err(err) => {
639+
if !err.is_expected() {
640+
tracing::warn!(
641+
remote_node = self.remote_node.address(),
642+
%chain_id,
643+
%err,
644+
"unexpected error from validator",
645+
);
646+
}
647+
return Err(err.into());
648+
}
610649
}
611650
}
612651
}

0 commit comments

Comments
 (0)