Skip to content

Commit 59536fa

Browse files
committed
Fix multi → single leader transition.
1 parent 819486d commit 59536fa

5 files changed

Lines changed: 115 additions & 25 deletions

File tree

linera-chain/src/manager.rs

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -379,18 +379,16 @@ where
379379
return Ok(false); // We are not a validator.
380380
};
381381
ensure!(
382-
round <= self.current_round(),
382+
round == self.current_round(),
383383
ChainError::WrongRound(self.current_round())
384384
);
385-
if round == self.current_round() {
386-
let Some(round_timeout) = *self.round_timeout.get() else {
387-
return Err(ChainError::RoundDoesNotTimeOut);
388-
};
389-
ensure!(
390-
local_time >= round_timeout,
391-
ChainError::NotTimedOutYet(round_timeout)
392-
);
393-
}
385+
let Some(round_timeout) = *self.round_timeout.get() else {
386+
return Err(ChainError::RoundDoesNotTimeOut);
387+
};
388+
ensure!(
389+
local_time >= round_timeout,
390+
ChainError::NotTimedOutYet(round_timeout)
391+
);
394392
if let Some(vote) = self.timeout_vote.get() {
395393
if vote.round == round {
396394
return Ok(false); // We already signed this timeout.
@@ -580,6 +578,12 @@ where
580578
.iter()
581579
.map(|proposal| proposal.content.round),
582580
)
581+
.chain(
582+
self.signed_proposal
583+
.get()
584+
.iter()
585+
.map(|proposal| proposal.content.round),
586+
)
583587
.max()
584588
.unwrap_or_default()
585589
.max(self.ownership.get().first_round());
@@ -687,7 +691,7 @@ where
687691
/// Sets the signed proposal, if it is newer than the known one, and not from a single-leader
688692
/// round. Returns whether it was updated.
689693
pub fn update_signed_proposal(&mut self, proposal: &BlockProposal) -> bool {
690-
if proposal.content.round > Round::MultiLeader(u32::MAX) {
694+
if proposal.content.round > Round::SingleLeader(0) {
691695
return false;
692696
}
693697
if let Some(old_proposal) = self.signed_proposal.get() {
@@ -765,7 +769,6 @@ pub struct ChainManagerInfo {
765769
#[debug(skip_if = Option::is_none)]
766770
pub requested_locking: Option<Box<LockingBlock>>,
767771
/// Latest timeout certificate we have seen.
768-
#[debug(skip_if = Option::is_none)]
769772
pub timeout: Option<Box<TimeoutCertificate>>,
770773
/// Latest vote we cast (either to validate or to confirm a block).
771774
#[debug(skip_if = Option::is_none)]

linera-core/src/client/mod.rs

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,10 @@ impl<Env: Environment> Client<Env> {
993993
let query = ChainInfoQuery::new(chain_id).with_manager_values();
994994
let remote_info = remote_node.handle_chain_info_query(query).await?;
995995

996-
info!("{:?}", remote_info);
996+
info!(
997+
"Info from {}: {:?}",
998+
remote_node.public_key, remote_info.manager
999+
);
9971000

9981001
if let Some(new_info) = self
9991002
.download_certificates_from(remote_node, chain_id, remote_info.next_block_height)
@@ -1920,15 +1923,15 @@ impl<Env: Environment> ChainClient<Env> {
19201923

19211924
self.synchronize_to_known_height().await?;
19221925

1923-
// if self.has_other_owners(&info.manager.ownership) {
1924-
// For chains with any owner other than ourselves, we could be missing recent
1925-
// certificates created by other owners. Further synchronize blocks from the network.
1926-
// This is a best-effort that depends on network conditions.
1926+
// if self.has_other_owners(&info.manager.ownership) {
1927+
// For chains with any owner other than ourselves, we could be missing recent
1928+
// certificates created by other owners. Further synchronize blocks from the network.
1929+
// This is a best-effort that depends on network conditions.
19271930
let info = self
19281931
.client
19291932
.maybe_synchronize_chain_state(self.chain_id)
19301933
.await?;
1931-
// }
1934+
// }
19321935

19331936
if info.epoch > self.client.admin_committees().await?.0 {
19341937
self.client
@@ -3035,7 +3038,10 @@ impl<Env: Environment> ChainClient<Env> {
30353038
.any(|proposal| proposal.content.round == manager.current_round)
30363039
|| (manager.current_round.is_fast() && has_oracle_responses);
30373040
info!("conflict: {}", conflict);
3038-
info!("next candidate round: {:?}", manager.ownership.next_round(manager.current_round));
3041+
info!(
3042+
"next candidate round: {:?}",
3043+
manager.ownership.next_round(manager.current_round)
3044+
);
30393045

30403046
let round = if !conflict {
30413047
manager.current_round
@@ -3052,7 +3058,12 @@ impl<Env: Environment> ChainClient<Env> {
30523058
"Conflicting proposal in the current round",
30533059
));
30543060
};
3055-
info!("can {} propose in round {}? {}", identity, round, manager.can_propose(identity, round));
3061+
info!(
3062+
"can {} propose in round {}? {}",
3063+
identity,
3064+
round,
3065+
manager.can_propose(identity, round)
3066+
);
30563067
if manager.can_propose(identity, round) {
30573068
return Ok(Either::Left(round));
30583069
}

linera-core/src/remote_node.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ impl<N: ValidatorNode> RemoteNode<N> {
113113
.await;
114114
match result {
115115
Err(NodeError::MissingCertificateValue) => {
116-
warn!(
116+
tracing::debug!(
117117
"Validator {} forgot a certificate value that they signed before",
118118
self.public_key
119119
);
@@ -135,7 +135,7 @@ impl<N: ValidatorNode> RemoteNode<N> {
135135
.await;
136136
match result {
137137
Err(NodeError::MissingCertificateValue) => {
138-
warn!(
138+
tracing::debug!(
139139
"Validator {} forgot a certificate value that they signed before",
140140
self.public_key
141141
);

linera-core/src/updater.rs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use linera_base::{
2222
};
2323
use linera_chain::{
2424
data_types::{BlockProposal, LiteVote},
25+
manager::LockingBlock,
2526
types::{ConfirmedBlock, GenericCertificate, ValidatedBlock, ValidatedBlockCertificate},
2627
};
2728
use linera_execution::{committee::Committee, system::EPOCH_STREAM_NAME};
@@ -451,6 +452,7 @@ where
451452
} else {
452453
tracing::warn!("NOPE 0");
453454
}
455+
// TODO: Deduplicate!!
454456
return Ok(());
455457
};
456458
// Figure out which certificates this validator is missing. In many cases, it's just the
@@ -517,7 +519,8 @@ where
517519
}
518520
}
519521
// If the remote node is missing a timeout certificate, send it as well.
520-
let local_info = self.local_node.chain_info(chain_id).await?;
522+
let query = ChainInfoQuery::new(chain_id).with_manager_values();
523+
let local_info = self.local_node.handle_chain_info_query(query).await?.info;
521524
if let Some(cert) = local_info.manager.timeout {
522525
if (local_info.next_block_height, cert.round) >= (remote_height, remote_round) {
523526
tracing::debug!("send_chain_information --> {:?}", cert);
@@ -528,6 +531,36 @@ where
528531
} else {
529532
tracing::debug!("send_chain_information NOPE 2");
530533
}
534+
if let Some(proposal) = local_info.manager.requested_proposed {
535+
tracing::info!("send_chain_information prop --> {:?}", proposal);
536+
if let Err(err) = Box::pin(self.send_block_proposal(proposal, vec![])).await {
537+
tracing::info!("Failed to propose: {err}");
538+
}
539+
}
540+
if let Some(proposal) = local_info.manager.requested_signed_proposal {
541+
tracing::info!("send_chain_information sig --> {:?}", proposal);
542+
if let Err(err) = Box::pin(self.send_block_proposal(proposal, vec![])).await {
543+
tracing::info!("Failed to propose: {err}");
544+
}
545+
}
546+
match local_info.manager.requested_locking.map(|b| *b) {
547+
Some(LockingBlock::Regular(validated)) => {
548+
if let Err(err) = self
549+
.remote_node
550+
.handle_optimized_validated_certificate(
551+
&validated,
552+
CrossChainMessageDelivery::NonBlocking,
553+
)
554+
.await
555+
{
556+
tracing::info!("Failed to send locking block: {err}");
557+
}
558+
}
559+
Some(LockingBlock::Fast(_)) => {
560+
tracing::error!("TODO: FAST")
561+
}
562+
None => {}
563+
}
531564
Ok(())
532565
}
533566

@@ -605,7 +638,50 @@ where
605638
}
606639
CommunicateAction::RequestTimeout { round, height, .. } => {
607640
let query = ChainInfoQuery::new(chain_id).with_timeout(height, round);
608-
let info = self.remote_node.handle_chain_info_query(query).await?;
641+
let info = match self
642+
.remote_node
643+
.handle_chain_info_query(query.clone())
644+
.await
645+
{
646+
Ok(info) => info,
647+
Err(NodeError::WrongRound(validator_round)) => {
648+
tracing::info!(
649+
"Failed to request timeout from validator {} because it sees \
650+
chain {} at round {} instead of {}.",
651+
self.remote_node.public_key,
652+
chain_id,
653+
validator_round,
654+
round
655+
);
656+
// The timeout is for a different round, so we need to update the validator.
657+
// TODO: this should probably be more specific as to which rounds are retried.
658+
self.send_chain_information(
659+
chain_id,
660+
height,
661+
CrossChainMessageDelivery::NonBlocking,
662+
)
663+
.await?;
664+
self.remote_node
665+
.handle_chain_info_query(query.clone())
666+
.await?
667+
}
668+
Err(NodeError::UnexpectedBlockHeight {
669+
expected_block_height,
670+
found_block_height,
671+
}) if expected_block_height < found_block_height => {
672+
// The proposal is for a later block height, so we need to update the validator.
673+
self.send_chain_information(
674+
chain_id,
675+
found_block_height,
676+
CrossChainMessageDelivery::NonBlocking,
677+
)
678+
.await?;
679+
self.remote_node
680+
.handle_chain_info_query(query.clone())
681+
.await?
682+
}
683+
Err(err) => Err(err)?,
684+
};
609685
info.manager.timeout_vote.ok_or_else(|| {
610686
NodeError::MissingVoteInValidatorResponse("request a timeout".into())
611687
})?

linera-rpc/src/grpc/client.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ impl GrpcClient {
7575
true
7676
}
7777
Code::Ok | Code::Cancelled | Code::ResourceExhausted => {
78-
info!("Unexpected gRPC status: {}; retrying", status);
78+
debug!("Unexpected gRPC status: {}; retrying", status);
7979
true
8080
}
8181
Code::NotFound => false, // This code is used if e.g. the validator is missing blobs.

0 commit comments

Comments
 (0)