diff --git a/node/Cargo.lock b/node/Cargo.lock index c9b74ebd..ab4361e6 100644 --- a/node/Cargo.lock +++ b/node/Cargo.lock @@ -320,7 +320,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools", "lazy_static", "lazycell", "log", @@ -676,7 +676,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools 0.10.5", + "itertools", "num-traits", "once_cell", "oorandom", @@ -697,7 +697,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -1746,15 +1746,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.12" @@ -2703,7 +2694,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" dependencies = [ "bytes", "heck", - "itertools 0.12.1", + "itertools", "log", "multimap", "once_cell", @@ -2723,7 +2714,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools", "proc-macro2", "quote", "syn", @@ -2988,9 +2979,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.17" +version = "0.23.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f1a745511c54ba6d4465e8d5dfbd81b45791756de28d4981af70d6dca128f1e" +checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" dependencies = [ "aws-lc-rs", "log", @@ -3833,9 +3824,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.3" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d157f1b96d14500ffdc1f10ba712e780825526c03d9a49b4d0324b0d9113ada" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", diff --git a/spec/informal-spec/replica.rs b/spec/informal-spec/replica.rs index a17b4dc2..1f4d6ac4 100644 --- a/spec/informal-spec/replica.rs +++ b/spec/informal-spec/replica.rs @@ -245,7 +245,7 @@ impl ReplicaState { self.phase = Phase::Prepare; // Send a new view message to the other replicas, for synchronization. - let new_view = NewView::new(self.get_justification(view)); + let new_view = NewView::new(self.create_justification()); self.send(new_view); } @@ -260,4 +260,4 @@ impl ReplicaState { Justification::Timeout(self.high_timeout_qc.unwrap()) } } -} \ No newline at end of file +} diff --git a/spec/informal-spec/types.rs b/spec/informal-spec/types.rs index d8074005..400bce84 100644 --- a/spec/informal-spec/types.rs +++ b/spec/informal-spec/types.rs @@ -72,7 +72,7 @@ impl Justification { match self { Commit(qc) => { // The previous proposal was finalized, so we can propose a new block. - (qc.block_number + 1, None) + (qc.vote.block_number + 1, None) } Timeout(qc) => { // Get the high vote of the timeout QC, if it exists. We check if there are @@ -88,7 +88,7 @@ impl Justification { let high_qc: Option = qc.high_commit_qc; if high_vote.is_some() - && (high_qc.is_none() || high_vote.block_number > high_qc.block_number) + && (high_qc.is_none() || high_vote.block_number > high_qc.vote.block_number) { // There was some proposal last view that might have been finalized. // We need to repropose it. @@ -98,7 +98,7 @@ impl Justification { // that it couldn't have been finalized. Either way, we can propose // a new block. let block_number = match high_qc { - Some(qc) => qc.block_number + 1, + Some(qc) => qc.vote.block_number + 1, None => 0, }; diff --git a/spec/protocol-spec/replica.qnt b/spec/protocol-spec/replica.qnt index f89ac1fd..ea44d11c 100644 --- a/spec/protocol-spec/replica.qnt +++ b/spec/protocol-spec/replica.qnt @@ -601,7 +601,7 @@ module replica { | Some(qc) => { val self1 = self.process_commit_qc(Some(qc)) - start_new_view(id, self1, signed_vote.vote.view + 1, Commit(qc)) + start_new_view(id, self1, signed_vote.vote.view + 1) } }, @@ -678,7 +678,7 @@ module replica { ...self.process_commit_qc(qc.high_commit_qc), high_timeout_qc: max_timeout_qc(qc_opt, self.high_timeout_qc) } - start_new_view(id, self1, signed_vote.vote.view + 1, Timeout(qc)), + start_new_view(id, self1, signed_vote.vote.view + 1), } }, // unchanged variables @@ -711,7 +711,7 @@ module replica { } if (new_view_view > self.view) { - start_new_view(id, self1, new_view_view, new_view.justification) + start_new_view(id, self1, new_view_view) } else { all { replica_state' = replica_state, @@ -731,7 +731,8 @@ module replica { // A step by the proposer for replica_id. // We pass new_block, which is used a substitute for: self.create_proposal(block_number). action proposer_step(replica_id: ReplicaId, new_block: Block): bool = all { - val replica_state_view = replica_state.get(replica_id).view + val replica = replica_state.get(replica_id) + val replica_state_view = replica.view val proposer_state_view = proposer_view.get(replica_id) all { // Check if we are in a new view and we are the leader. @@ -745,7 +746,7 @@ module replica { // Get the justification for this view. If we have both a commit QC // and a timeout QC for this view (highly unlikely), we should prefer // to use the commit QC. - val justification = ghost_justifications.get((replica_id, replica_state_view)) + val justification = replica.create_justification() // Get the block number and check if this must be a reproposal. val block_and_hash = justification.get_implied_block() val block_number = block_and_hash._1 @@ -798,8 +799,9 @@ module replica { } // a correct replica changes to a new view - action start_new_view(id: ReplicaId, self: ReplicaState, view: ViewNumber, justification: Justification): bool = { + action start_new_view(id: ReplicaId, self: ReplicaState, view: ViewNumber): bool = { // make sure that we do not go out of bounds with the new view + val justification = self.create_justification() all { VIEWS.contains(view), // switch to the Prepare phase @@ -1016,6 +1018,26 @@ module replica { } } + def create_justification(self: ReplicaState): Justification = { + // We always have a timeout QC, at least, for the view 0. + // However, the type checker needs a well-typed value. Hence, use empty_tqc. + val empty_tqc: TimeoutQC = { + votes: Map(), agg_sig: Set(), high_commit_qc: None, ghost_view: 0 + } + val high_tqc = self.high_timeout_qc.unwrap_or(empty_tqc) + match (self.high_commit_qc) { + | None => + Timeout(high_tqc) + + | Some(cqc) => + if (cqc.vote.view >= high_tqc.ghost_view) { + Commit(cqc) + } else { + Timeout(high_tqc) + } + } + } + def justification_verify(justification: Justification): bool = { match (justification) { | Timeout(qc) => @@ -1675,4 +1697,18 @@ module replica { val views_over_2_example = CORRECT.forall(id => { replica_state.get(id).view <= 2 }) + + // check this falsy invariant to see an example of a replica having + // both a high commit QC and a timeout QC for the same view + val high_commit_and_timeout_qc_example = CORRECT.forall(id => { + val self = replica_state.get(id) + match (self.high_commit_qc) { + | None => true + | Some(cqc) => + match (self.high_timeout_qc) { + | None => true + | Some(tqc) => cqc.vote.view != tqc.ghost_view + } + } + }) } \ No newline at end of file