From dd3af462b26d4f7c626ad1b6ea857abe77738aa3 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 9 Jan 2023 10:24:45 +0200 Subject: [PATCH 01/38] Manual merge with latest proof additions. We completed the proof refactoring to improve verification times by manually guiding the automation on the last stable point where the proof passes correctly without timeouts. We are now transferring the latest progress in the proof to the now improved structure of the proof. --- docs/sbft-formal-model/proof.dfy | 418 ++++++++++++++++++++++++++- docs/sbft-formal-model/replica.i.dfy | 15 + 2 files changed, 431 insertions(+), 2 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 081276d746..49a7b7d36c 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -29,6 +29,8 @@ module Proof { import opened SafetySpec import opened Library + type Message = Network.Message + predicate IsHonestReplica(c:Constants, hostId:HostId) { && c.WF() @@ -283,6 +285,7 @@ module Proof { predicate Inv(c: Constants, v:Variables) { //&& PrePreparesCarrySameClientOpsForGivenSeqID(c, v) // Do not remove, lite invariant about internal honest Node invariants: + && v.WF(c) && AllReplicasLiteInv(c, v) && RecordedPreparesHaveValidSenderID(c, v) //&& SentPreparesMatchRecordedPrePrepareIfHostInSameView(c, v) @@ -300,10 +303,11 @@ module Proof { && HonestReplicasLockOnCommitForGivenView(c, v) && CommitMsgsFromHonestSendersAgree(c, v) && RecordedCheckpointsRecvdCameFromNetwork(c, v) + && UnCommitableAgreesWithPrepare(c, v) } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, - operationWrapper:Messages.OperationWrapper) : set> + operationWrapper:Messages.OperationWrapper) : set requires v.WF(c) { set msg | && msg in v.network.sentMsgs @@ -323,7 +327,7 @@ module Proof { } lemma WlogCommitAgreement(c: Constants, v:Variables, v':Variables, step:Step, - msg1:Network.Message, msg2:Network.Message) + msg1:Message, msg2:Message) requires Inv(c, v) requires NextStep(c, v, v', step) requires msg1 in v'.network.sentMsgs @@ -709,6 +713,130 @@ module Proof { h_v.workingWindow.reveal_Shift(); } + lemma TriviallyPreserveUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires || (&& HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + && (|| h_step.SendPrePrepareStep? + || h_step.RecvPrePrepareStep? + || h_step.RecvPrepareStep? + || h_step.RecvCommitStep? + || h_step.DoCommitStep? + || h_step.ExecuteStep? + || h_step.SendCheckpointStep? + || h_step.RecvCheckpointStep? + || h_step.AdvanceWorkingWindowStep? + || h_step.PerformStateTransferStep? + || h_step.SendViewChangeMsgStep? + || h_step.RecvViewChangeMsgStep? + || h_step.SelectQuorumOfViewChangeMsgsStep? + || h_step.SendNewViewMsgStep? + || h_step.RecvNewViewMsgStep? + || h_step.SendCommitStep?)) + || (NextStep(c, v, v', step) && c.clusterConfig.IsFaultyReplica(step.id)) + || (NextStep(c, v, v', step) && c.clusterConfig.IsClient(step.id)) + ensures UnCommitableAgreesWithPrepare(c, v') + { + reveal_UnCommitableAgreesWithPrepare(); + forall prepareMsg:Message, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper + | && prepareMsg.payload.Prepare? + && priorView < prepareMsg.payload.view + && c.clusterConfig.IsHonestReplica(prepareMsg.sender) + && priorOperationWrapper != prepareMsg.payload.operationWrapper + && prepareMsg in v'.network.sentMsgs + ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| + // == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; + } + } + + lemma HonestLeaveViewStepPreservesUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + requires h_step.LeaveViewStep? + ensures UnCommitableAgreesWithPrepare(c, v') + { + reveal_UnCommitableAgreesWithPrepare(); + forall prepareMsg:Message, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper + | && prepareMsg.payload.Prepare? + && priorView < prepareMsg.payload.view + && c.clusterConfig.IsHonestReplica(prepareMsg.sender) + && priorOperationWrapper != prepareMsg.payload.operationWrapper + && prepareMsg in v'.network.sentMsgs + ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + // assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + // <= ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| + // <= |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; + Library.SubsetCardinality(ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper), + ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)); + } + } + + lemma HonestSendPrepareStepPreservesUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + requires h_step.SendPrepareStep? + ensures UnCommitableAgreesWithPrepare(c, v') + { + /// + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_AllReplicasLiteInv(); + reveal_RecordedPreparesHaveValidSenderID(); + reveal_RecordedPrePreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesMatchHostView(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); + reveal_RecordedCommitsClientOpsMatchPrePrepare(); + reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_HonestReplicasLockOnPrepareForGivenView(); + reveal_HonestReplicasLockOnCommitForGivenView(); + reveal_CommitMsgsFromHonestSendersAgree(); + reveal_RecordedCheckpointsRecvdCameFromNetwork(); + reveal_HonestReplicasLockOnCommitForGivenView(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + h_v.workingWindow.reveal_Shift(); + /// + + // if (h_step.SendPrepareStep?) { + // assume false; + // } else if (h_step.LeaveViewStep?) { + // assume false; + // } else if (h_step.SendCommitStep?) { + // assume false; + // } + + reveal_UnCommitableAgreesWithPrepare(); + forall prepareMsg:Message, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper + | && prepareMsg.payload.Prepare? + && priorView < prepareMsg.payload.view + && c.clusterConfig.IsHonestReplica(prepareMsg.sender) + && priorOperationWrapper != prepareMsg.payload.operationWrapper + && prepareMsg in v'.network.sentMsgs + ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| + == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; + // assert |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); + } + } + + /// End Of Case Splitting + lemma QuorumOfPreparesInNetworkMonotonic(c: Constants, v:Variables, v':Variables, step:Step) requires NextStep(c, v, v', step) ensures (forall view, seqID, clientOp | QuorumOfPreparesInNetwork(c, v, view, seqID, clientOp) @@ -723,6 +851,277 @@ module Proof { } } + predicate AgreementQuorumOfMessages(c:Constants, msgs:set) { + && c.WF() + && |msgs| >= c.clusterConfig.AgreementQuorum() + && Messages.UniqueSenders(msgs) + } + + predicate isCommitQuorum(c:Constants, + v:Variables, + view:nat, + seqID:Messages.SequenceID, + operationWrapper:Messages.OperationWrapper, + sentCommits:set) + requires v.WF(c) + { + && sentCommits <= v.network.sentMsgs + && AgreementQuorumOfMessages(c, sentCommits) + && (forall msg | msg in sentCommits + :: && msg.payload.Commit? + && msg.payload.view == view + && msg.payload.seqID == seqID + && msg.payload.operationWrapper == operationWrapper + && msg.sender in getAllReplicas(c)) + } + + lemma UniqueSendersCardinality(msgs:set) + requires Messages.UniqueSenders(msgs) + ensures |Messages.sendersOf(msgs)| == |msgs| + { + Messages.reveal_UniqueSenders(); + if |msgs| > 0 { + var m :| m in msgs; + var subMsgs := msgs - {m}; + UniqueSendersCardinality(subMsgs); + assert Messages.sendersOf(msgs) == Messages.sendersOf(subMsgs) + {m.sender}; + } + } + + function ReplicasThatCanCommitInView(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper) + : set + requires v.WF(c) + { + var replicasSentCommit := set replica | + && replica in getAllReplicas(c) + && c.clusterConfig.IsHonestReplica(replica) + && Network.Message(replica, + Messages.Commit(view, + seqID, + operationWrapper)) in v.network.sentMsgs; + var inViewOrLower := set replica | && replica in getAllReplicas(c) + && c.clusterConfig.IsHonestReplica(replica) + && var h_c := c.hosts[replica].replicaConstants; + && var h_v := v.hosts[replica].replicaVariables; + && h_v.view <= view; + var faultyReplicas := set replica | && replica in getAllReplicas(c) + && c.clusterConfig.IsFaultyReplica(replica); + replicasSentCommit + inViewOrLower + faultyReplicas + } + + predicate UnCommitableInView(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper) { + && v.WF(c) + && |ReplicasThatCanCommitInView(c, v, seqID, view, operationWrapper)| < c.clusterConfig.AgreementQuorum() + } + + // UnCommitable agrees with Prepares. + predicate {:opaque} UnCommitableAgreesWithPrepare(c:Constants, v:Variables) { + && v.WF(c) + && (forall prepareMsg:Message, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper + | && prepareMsg.payload.Prepare? + && priorView < prepareMsg.payload.view + && c.clusterConfig.IsHonestReplica(prepareMsg.sender) + && priorOperationWrapper != prepareMsg.payload.operationWrapper + && prepareMsg in v.network.sentMsgs + :: UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)) + } + +//TODO: write a predicate Prepare matches Commit + lemma GetPrepareFromHonestSenderForCommit(c:Constants, v:Variables, commitMsg:Message) + returns (prepare:Message) + requires Inv(c, v) + requires commitMsg.payload.Commit? + requires commitMsg in v.network.sentMsgs + requires c.clusterConfig.IsHonestReplica(commitMsg.sender) + ensures prepare.payload.Prepare? + ensures c.clusterConfig.IsHonestReplica(prepare.sender) + ensures prepare in v.network.sentMsgs + ensures prepare.payload.view == commitMsg.payload.view + ensures prepare.payload.seqID == commitMsg.payload.seqID + ensures prepare.payload.operationWrapper == commitMsg.payload.operationWrapper + { + reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + var prepares := sentPreparesForSeqID(c, v, commitMsg.payload.view, commitMsg.payload.seqID, commitMsg.payload.operationWrapper); + prepare := GetMsgFromHonestSender(c, v, prepares); + } + + function FaultyReplicas(c: Constants) : (faulty:set) + requires c.WF() + { + set sender | 0 <= sender < c.clusterConfig.N() && !IsHonestReplica(c, sender) + } + + lemma CountFaultyReplicas(c: Constants) + requires c.WF() + ensures |FaultyReplicas(c)| <= c.clusterConfig.F() + { + var count := 0; + var f := c.clusterConfig.F(); + var n := c.clusterConfig.N(); + while(count < n) + invariant count <= n + invariant |set sender | 0 <= sender < count && !IsHonestReplica(c, sender)| == if count <= f then count + else f + { + var oldSet := set sender | 0 <= sender < count && !IsHonestReplica(c, sender); + var newSet := set sender | 0 <= sender < count + 1 && !IsHonestReplica(c, sender); + if(count + 1 <= f) { + assert newSet == oldSet + {count}; + } else { + assert newSet == oldSet; + } + count := count + 1; + } + } + + lemma FindHonestNode(c: Constants, replicas:set) + returns (honest:HostIdentifiers.HostId) + requires c.WF() + requires |replicas| >= c.clusterConfig.ByzantineSafeQuorum() + requires replicas <= getAllReplicas(c) + ensures IsHonestReplica(c, honest) + ensures honest in replicas + { + var honestReplicas := replicas * HonestReplicas(c); + CountFaultyReplicas(c); + if(|honestReplicas| == 0) { + Library.SubsetCardinality(replicas, FaultyReplicas(c)); + assert false; //Proof by contradiction + } + + var result :| result in honestReplicas; + honest := result; + } + + lemma GetMsgFromHonestSender(c:Constants, v:Variables, quorum:set) + returns (message:Message) + requires Inv(c, v) + requires |Messages.sendersOf(quorum)| >= c.clusterConfig.AgreementQuorum() + requires Messages.sendersOf(quorum) <= getAllReplicas(c) + ensures c.clusterConfig.IsHonestReplica(message.sender) + ensures message in quorum + { + var senders := Messages.sendersOf(quorum); + var honestSender := FindHonestNode(c, senders); + var honest :| honest in quorum && honest.sender == honestSender; + message := honest; + } + + lemma FindHonestCommitInQuorum(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + commits:set, + operationWrapper:Messages.OperationWrapper) + returns (honestCommit:Message) + requires Inv(c, v) + requires isCommitQuorum(c, v, view, seqID, operationWrapper, commits) + ensures honestCommit in commits + ensures IsHonestReplica(c, honestCommit.sender) + { + var senders := Messages.sendersOf(commits); + UniqueSendersCardinality(commits); + var honestSender := FindHonestNode(c, senders); + var honest :| honest in commits && honest.sender == honestSender; + honestCommit := honest; + } + + lemma CommitQuorumAgree(c:Constants, v:Variables) + requires Inv(c, v) + ensures CommitQuorumsAgreeOnOperation(c, v) + { + forall seqID:Messages.SequenceID, + view1:nat, + view2:nat, + commits1:set, + commits2:set, + operationWrapper1:Messages.OperationWrapper, + operationWrapper2:Messages.OperationWrapper + | && isCommitQuorum(c, v, view1, seqID, operationWrapper1, commits1) + && isCommitQuorum(c, v, view2, seqID, operationWrapper2, commits2) + ensures (operationWrapper1 == operationWrapper2) { + if view1 == view2 { + // var honestCommit1 := FindHonestCommitInQuorum(); + var commit1 := FindHonestCommitInQuorum(c, v, seqID, view1, commits1, operationWrapper1); + var commit2 := FindHonestCommitInQuorum(c, v, seqID, view2, commits2, operationWrapper2); + assert commit1 in v.network.sentMsgs; // Trigger for CommitMsgsFromHonestSendersAgree + assert commit2 in v.network.sentMsgs; // Trigger for CommitMsgsFromHonestSendersAgree + reveal_CommitMsgsFromHonestSendersAgree(); + assert commit1.payload.operationWrapper == commit2.payload.operationWrapper; + assert operationWrapper1 == operationWrapper2; + } else if view1 < view2 { + CommitQuorumAgreeDifferentViews(c, v, seqID, view1, view2, commits1, commits2, operationWrapper1, operationWrapper2); + } else { + CommitQuorumAgreeDifferentViews(c, v, seqID, view2, view1, commits2, commits1, operationWrapper2, operationWrapper1); + } + } + } + + lemma CommitQuorumAgreeDifferentViews(c:Constants, v:Variables,seqID:Messages.SequenceID, + view1:nat, + view2:nat, + commits1:set, + commits2:set, + operationWrapper1:Messages.OperationWrapper, + operationWrapper2:Messages.OperationWrapper) + requires Inv(c, v) + requires isCommitQuorum(c, v, view1, seqID, operationWrapper1, commits1) + requires isCommitQuorum(c, v, view2, seqID, operationWrapper2, commits2) + requires view1 < view2 + ensures (operationWrapper1 == operationWrapper2) + { + reveal_UnCommitableAgreesWithPrepare(); + if operationWrapper1 != operationWrapper2 { + UniqueSendersCardinality(commits2); + var commit := GetMsgFromHonestSender(c, v, commits2); + var prepare := GetPrepareFromHonestSenderForCommit(c, v, commit); // This term instantiates UnCommitableAgreesWithPrepare + UniqueSendersCardinality(commits1); + Library.SubsetCardinality(Messages.sendersOf(commits1), ReplicasThatCanCommitInView(c, v, seqID, view1, operationWrapper1)); + assert !UnCommitableInView(c, v, seqID, view1, operationWrapper1); // We have proven both UnCommitableInView !UnCommitableInView + assert false; //proof by contradiction + } + } + + // Quorum of commits >= 2F+1 agree on all views + predicate CommitQuorumsAgreeOnOperation(c:Constants, v:Variables) { + && v.WF(c) + && (forall seqID:Messages.SequenceID, + view1:nat, + view2:nat, + commits1:set, + commits2:set, + operationWrapper1:Messages.OperationWrapper, + operationWrapper2:Messages.OperationWrapper + | && isCommitQuorum(c, v, view1, seqID, operationWrapper1, commits1) + && isCommitQuorum(c, v, view2, seqID, operationWrapper2, commits2) + :: operationWrapper1 == operationWrapper2) + } + + predicate HonestReplicasAgreeOnOperationsOrdering(c:Constants, v:Variables) { + && v.WF(c) + && (forall replica1:HostId, + replica2:HostId, + seqID:Messages.SequenceID | && IsHonestReplica(c, replica1) + && IsHonestReplica(c, replica2) + :: && var r1_c := c.hosts[replica1].replicaConstants; + && var r2_c := c.hosts[replica2].replicaConstants; + && var r1_v := v.hosts[replica1].replicaVariables; + && var r2_v := v.hosts[replica2].replicaVariables; + && (&& Replica.IsCommitted(r1_c, r1_v, seqID) + && Replica.IsCommitted(r2_c, r2_v, seqID)) ==> Replica.GetCommittedOperation(r1_c, r1_v, seqID) == Replica.GetCommittedOperation(r2_c, r2_v, seqID)) + + } + lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') @@ -751,6 +1150,15 @@ module Proof { HonestPreservesHonestReplicasLockOnCommitForGivenView(c, v, v', step, h_v, h_step); HonestPreservesCommitMsgsFromHonestSendersAgree(c, v, v', step, h_v, h_step); HonestPreservesRecordedCheckpointsRecvdCameFromNetwork(c, v, v', step, h_v, h_step); + //TODO: extract into a lemma. + if(h_step.LeaveViewStep?) { + HonestLeaveViewStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } else if(h_step.SendPrepareStep?) { + HonestSendPrepareStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } else { + TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } + //END } else { InvNextFaultyOrClient(c, v, v', step); } @@ -814,6 +1222,12 @@ module Proof { assert EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v') by { AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v, v', step); } + assert UnCommitableAgreesWithPrepare(c, v') by { + var h_step :| true; + var h_v :| true; + TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } + } lemma InvariantInductive(c: Constants, v:Variables, v':Variables) diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index d4c20a6731..2d2e07b5b0 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -698,6 +698,21 @@ module Replica { .(committedClientOperations := checkpointsQuorum.prototype().committedClientOperations) } + // All Checkpoints recorded are in network + // msgs in Checkpoints Quorum are also in network + // Wahtever a replica has written down agrees with 2F+1 other + // Quorum of commits agree on all views + + predicate IsCommitted(c:Constants, v:Variables, seqID:SequenceID) { + && seqID in v.committedClientOperations + } + + function GetCommittedOperation(c:Constants, v:Variables, seqID:SequenceID) : OperationWrapper + requires IsCommitted(c, v, seqID) + { + v.committedClientOperations[seqID] + } + predicate Init(c:Constants, v:Variables) { && v.WF(c) && v.view == 0 From 35cd76492b1f00e10127c6a80311529beca1d9ec Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 11 Jan 2023 11:51:20 +0200 Subject: [PATCH 02/38] Code restructuring for clarity and better automation handling. ReplicasThatSentCommit and ReplicasInViewOrLower predicates added for use in ReplicasThatCanCommitInView. Added predicates RecordedPrePreparesMatchHostView and UnCommitableAgreesWithRecordedPrePrepare to the global invariant. --- docs/sbft-formal-model/proof.dfy | 144 +++++++++++++++++++++---------- 1 file changed, 100 insertions(+), 44 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 49a7b7d36c..d10ee2f60c 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -291,6 +291,7 @@ module Proof { //&& SentPreparesMatchRecordedPrePrepareIfHostInSameView(c, v) && RecordedPrePreparesRecvdCameFromNetwork(c, v) && RecordedPreparesInAllHostsRecvdCameFromNetwork(c, v) + && RecordedPrePreparesMatchHostView(c, v) && RecordedPreparesMatchHostView(c, v) && EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v) && RecordedPreparesClientOpsMatchPrePrepare(c, v) @@ -304,6 +305,7 @@ module Proof { && CommitMsgsFromHonestSendersAgree(c, v) && RecordedCheckpointsRecvdCameFromNetwork(c, v) && UnCommitableAgreesWithPrepare(c, v) + && UnCommitableAgreesWithRecordedPrePrepare(c, v) } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, @@ -787,25 +789,27 @@ module Proof { ensures UnCommitableAgreesWithPrepare(c, v') { /// - reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - reveal_AllReplicasLiteInv(); - reveal_RecordedPreparesHaveValidSenderID(); - reveal_RecordedPrePreparesRecvdCameFromNetwork(); - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); - reveal_RecordedPreparesMatchHostView(); - reveal_RecordedPreparesClientOpsMatchPrePrepare(); - reveal_RecordedCommitsClientOpsMatchPrePrepare(); - reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); - reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); - reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - reveal_HonestReplicasLockOnPrepareForGivenView(); - reveal_HonestReplicasLockOnCommitForGivenView(); - reveal_CommitMsgsFromHonestSendersAgree(); - reveal_RecordedCheckpointsRecvdCameFromNetwork(); - reveal_HonestReplicasLockOnCommitForGivenView(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); - h_v.workingWindow.reveal_Shift(); + // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + // reveal_AllReplicasLiteInv(); + // reveal_RecordedPreparesHaveValidSenderID(); + // reveal_RecordedPrePreparesRecvdCameFromNetwork(); + // reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + // reveal_RecordedPreparesMatchHostView(); + // reveal_RecordedPreparesClientOpsMatchPrePrepare(); + // reveal_RecordedCommitsClientOpsMatchPrePrepare(); + // reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + // reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + // reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + // reveal_HonestReplicasLockOnPrepareForGivenView(); + // reveal_HonestReplicasLockOnCommitForGivenView(); + // reveal_CommitMsgsFromHonestSendersAgree(); + // reveal_RecordedCheckpointsRecvdCameFromNetwork(); + // reveal_HonestReplicasLockOnCommitForGivenView(); + // reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + // h_v.workingWindow.reveal_Shift(); + reveal_UnCommitableAgreesWithRecordedPrePrepare(); + reveal_RecordedPrePreparesMatchHostView(); /// // if (h_step.SendPrepareStep?) { @@ -826,11 +830,22 @@ module Proof { && priorOperationWrapper != prepareMsg.payload.operationWrapper && prepareMsg in v'.network.sentMsgs ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { - assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); - assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) - == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); - assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| - == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; + if prepareMsg in v.network.sentMsgs { + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + } else { + assert h_v.workingWindow.prePreparesRcvd[prepareMsg.payload.seqID].Some?; + var prePrepareMsg := h_v.workingWindow.prePreparesRcvd[prepareMsg.payload.seqID].value; + assert prePrepareMsg.payload.seqID == prepareMsg.payload.seqID; + assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + } + + // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| + // == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; // assert |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); } } @@ -888,29 +903,41 @@ module Proof { } } + predicate ReplicasThatSentCommit(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper, + replica:HostIdentifiers.HostId) { + && c.clusterConfig.IsHonestReplica(replica) + && Network.Message(replica, + Messages.Commit(view, + seqID, + operationWrapper)) in v.network.sentMsgs + } + + predicate ReplicasInViewOrLower(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper, + replica:HostIdentifiers.HostId) { + && c.clusterConfig.IsHonestReplica(replica) + && v.hosts[replica].replicaVariables.view <= view + } + function ReplicasThatCanCommitInView(c:Constants, - v:Variables, - seqID:Messages.SequenceID, - view:nat, - operationWrapper:Messages.OperationWrapper) - : set + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper) + : set requires v.WF(c) { - var replicasSentCommit := set replica | - && replica in getAllReplicas(c) - && c.clusterConfig.IsHonestReplica(replica) - && Network.Message(replica, - Messages.Commit(view, - seqID, - operationWrapper)) in v.network.sentMsgs; - var inViewOrLower := set replica | && replica in getAllReplicas(c) - && c.clusterConfig.IsHonestReplica(replica) - && var h_c := c.hosts[replica].replicaConstants; - && var h_v := v.hosts[replica].replicaVariables; - && h_v.view <= view; - var faultyReplicas := set replica | && replica in getAllReplicas(c) - && c.clusterConfig.IsFaultyReplica(replica); - replicasSentCommit + inViewOrLower + faultyReplicas + set replica | replica in getAllReplicas(c) && + (|| ReplicasThatSentCommit(c, v, seqID, view, operationWrapper, replica) + || ReplicasInViewOrLower(c, v, seqID, view, operationWrapper, replica) + || c.clusterConfig.IsFaultyReplica(replica)) } predicate UnCommitableInView(c:Constants, @@ -936,6 +963,35 @@ module Proof { :: UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)) } + predicate {:opaque} UnCommitableAgreesWithRecordedPrePrepare(c:Constants, v:Variables) { + && v.WF(c) + && (forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v.hosts[replicaIdx].replicaVariables; + && var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) + && var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? + && priorOperationWrapper != prePrepareMsg.payload.operationWrapper + && priorView < prePrepareMsg.payload.view + :: && var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper)) + + } + + predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { + && v.WF(c) + && (forall observer, seqID, sender | + && IsHonestReplica(c, observer) + && c.clusterConfig.IsReplica(sender) + && var replicaVars := v.hosts[observer].replicaVariables; + && seqID in replicaVars.workingWindow.preparesRcvd + && replicaVars.workingWindow.prePreparesRcvd[seqID].Some? + :: && var replicaVars := v.hosts[observer].replicaVariables; + && replicaVars.view == replicaVars.workingWindow.prePreparesRcvd[seqID].value.payload.view) + } + + //TODO: write a predicate Prepare matches Commit lemma GetPrepareFromHonestSenderForCommit(c:Constants, v:Variables, commitMsg:Message) returns (prepare:Message) From 2350872140127e7edba44bf0dcc1d873b2070ff4 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 12 Jan 2023 22:58:15 +0200 Subject: [PATCH 03/38] Progress in the proof. * Addition to Inv predicate RecordedPrePreparesRecvdCameFromNetwork to retain the knowledge that the key we stored (seqID) is equal to the seqID in the msg. * Refactor TriviallyPreserveUnCommitableAgreesWithPrepare and * TriviallyPreserveUnCommitableAgreesWithRecordedPrePrepare to take as argument an option of either honest or not honest replica. * Necessary reveals for InitEstablishesInv extracted to separate lemma. * Lemmas for proving the additional predicates in the Inv added. --- docs/sbft-formal-model/proof.dfy | 270 ++++++++++++++++++++++--------- 1 file changed, 196 insertions(+), 74 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index d10ee2f60c..977b48514b 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -46,7 +46,10 @@ module Proof { && var replicaConstants := c.hosts[replicaIdx].replicaConstants; && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? - :: v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value in v.network.sentMsgs) + :: (&& var msg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && msg in v.network.sentMsgs + && msg.payload.seqID == seqID)) // The key we stored matches what is in the msg + } predicate RecordedPreparesRecvdCameFromNetwork(c:Constants, v:Variables, observer:HostId) @@ -715,46 +718,92 @@ module Proof { h_v.workingWindow.reveal_Shift(); } - lemma TriviallyPreserveUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + datatype HonestInfo = HonestInfo(h_v:Replica.Variables, h_step:Replica.Step) | NotHonest() + + lemma TriviallyPreserveUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, info:HonestInfo) requires Inv(c, v) - requires || (&& HonestReplicaStepTaken(c, v, v', step, h_v, h_step) - && (|| h_step.SendPrePrepareStep? - || h_step.RecvPrePrepareStep? - || h_step.RecvPrepareStep? - || h_step.RecvCommitStep? - || h_step.DoCommitStep? - || h_step.ExecuteStep? - || h_step.SendCheckpointStep? - || h_step.RecvCheckpointStep? - || h_step.AdvanceWorkingWindowStep? - || h_step.PerformStateTransferStep? - || h_step.SendViewChangeMsgStep? - || h_step.RecvViewChangeMsgStep? - || h_step.SelectQuorumOfViewChangeMsgsStep? - || h_step.SendNewViewMsgStep? - || h_step.RecvNewViewMsgStep? - || h_step.SendCommitStep?)) - || (NextStep(c, v, v', step) && c.clusterConfig.IsFaultyReplica(step.id)) - || (NextStep(c, v, v', step) && c.clusterConfig.IsClient(step.id)) - ensures UnCommitableAgreesWithPrepare(c, v') - { - reveal_UnCommitableAgreesWithPrepare(); - forall prepareMsg:Message, - priorView:nat, - priorOperationWrapper:Messages.OperationWrapper - | && prepareMsg.payload.Prepare? - && priorView < prepareMsg.payload.view - && c.clusterConfig.IsHonestReplica(prepareMsg.sender) - && priorOperationWrapper != prepareMsg.payload.operationWrapper - && prepareMsg in v'.network.sentMsgs - ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { - assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); - assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) - == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); - // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| - // == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; - } + requires match(info) { + case HonestInfo(h_v, h_step) => (&& HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + && !h_step.SendPrepareStep? + && !h_step.LeaveViewStep?) + case NotHonest() => (|| (NextStep(c, v, v', step) && c.clusterConfig.IsFaultyReplica(step.id)) + || (NextStep(c, v, v', step) && c.clusterConfig.IsClient(step.id))) + } + ensures UnCommitableAgreesWithPrepare(c, v') + { + reveal_UnCommitableAgreesWithPrepare(); + forall prepareMsg:Message, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper + | && prepareMsg.payload.Prepare? + && priorView < prepareMsg.payload.view + && c.clusterConfig.IsHonestReplica(prepareMsg.sender) + && priorOperationWrapper != prepareMsg.payload.operationWrapper + && prepareMsg in v'.network.sentMsgs + ensures UnCommitableInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) { + assert UnCommitableInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); + // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| + // == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; } + } + + lemma TriviallyPreserveUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, info:HonestInfo) + requires Inv(c, v) + requires match(info) { + case HonestInfo(h_v, h_step) => (&& HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + && !h_step.RecvPrePrepareStep? + && !h_step.LeaveViewStep?) + case NotHonest() => (|| (NextStep(c, v, v', step) && c.clusterConfig.IsFaultyReplica(step.id)) + || (NextStep(c, v, v', step) && c.clusterConfig.IsClient(step.id))) + } + ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') + { + reveal_UnCommitableAgreesWithRecordedPrePrepare(); + forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; + && var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) + && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && priorOperationWrapper != prePrepareMsg.payload.operationWrapper + && priorView < prePrepareMsg.payload.view + ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { + var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) + == ReplicasThatCanCommitInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + } + } + + lemma HonestLeaveViewStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + requires h_step.LeaveViewStep? + ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') + { + reveal_UnCommitableAgreesWithRecordedPrePrepare(); + forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; + && var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) + && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && priorOperationWrapper != prePrepareMsg.payload.operationWrapper + && priorView < prePrepareMsg.payload.view + ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { + var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + Library.SubsetCardinality(ReplicasThatCanCommitInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper), + ReplicasThatCanCommitInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper)); + + } + } lemma HonestLeaveViewStepPreservesUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) @@ -792,7 +841,7 @@ module Proof { // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); // reveal_AllReplicasLiteInv(); // reveal_RecordedPreparesHaveValidSenderID(); - // reveal_RecordedPrePreparesRecvdCameFromNetwork(); + reveal_RecordedPrePreparesRecvdCameFromNetwork(); // reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); // reveal_RecordedPreparesMatchHostView(); // reveal_RecordedPreparesClientOpsMatchPrePrepare(); @@ -821,6 +870,7 @@ module Proof { // } reveal_UnCommitableAgreesWithPrepare(); + //TODO: minimise proof forall prepareMsg:Message, priorView:nat, priorOperationWrapper:Messages.OperationWrapper @@ -843,6 +893,7 @@ module Proof { assert ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper) == ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper); } + //END TODO // assert |ReplicasThatCanCommitInView(c, v', prepareMsg.payload.seqID, priorView, priorOperationWrapper)| // == |ReplicasThatCanCommitInView(c, v, prepareMsg.payload.seqID, priorView, priorOperationWrapper)|; @@ -908,7 +959,9 @@ module Proof { seqID:Messages.SequenceID, view:nat, operationWrapper:Messages.OperationWrapper, - replica:HostIdentifiers.HostId) { + replica:HostIdentifiers.HostId) + requires v.WF(c) + { && c.clusterConfig.IsHonestReplica(replica) && Network.Message(replica, Messages.Commit(view, @@ -921,7 +974,9 @@ module Proof { seqID:Messages.SequenceID, view:nat, operationWrapper:Messages.OperationWrapper, - replica:HostIdentifiers.HostId) { + replica:HostIdentifiers.HostId) + requires v.WF(c) + { && c.clusterConfig.IsHonestReplica(replica) && v.hosts[replica].replicaVariables.view <= view } @@ -970,8 +1025,8 @@ module Proof { && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && var replicaConstants := c.hosts[replicaIdx].replicaConstants; && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) - && var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables.workingWindow.prePreparesRcvd[seqID].value; && priorOperationWrapper != prePrepareMsg.payload.operationWrapper && priorView < prePrepareMsg.payload.view :: && var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; @@ -1178,6 +1233,65 @@ module Proof { } + lemma HonestPreservesRecordedPrePreparesMatchHostView(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures RecordedPrePreparesMatchHostView(c, v') + { + h_v.workingWindow.reveal_Shift(); + reveal_RecordedPrePreparesMatchHostView(); + } + + lemma HonestPreservesUnCommitableAgreesWithPrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures UnCommitableAgreesWithPrepare(c, v') + { + if(h_step.LeaveViewStep?) { + HonestLeaveViewStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } else if(h_step.SendPrepareStep?) { + HonestSendPrepareStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + } else { + TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, HonestInfo(h_v, h_step)); + } + } + + lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + requires h_step.RecvPrePrepareStep? + ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') + { + reveal_UnCommitableAgreesWithRecordedPrePrepare(); + forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; + && var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) + && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && priorOperationWrapper != prePrepareMsg.payload.operationWrapper + && priorView < prePrepareMsg.payload.view + ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { + + } + } + + lemma HonestPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') + { + if(h_step.LeaveViewStep?) { + HonestLeaveViewStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); + } else if(h_step.RecvPrePrepareStep?) { + HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); + } else { + TriviallyPreserveUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, HonestInfo(h_v, h_step)); + } + } + lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') @@ -1206,15 +1320,9 @@ module Proof { HonestPreservesHonestReplicasLockOnCommitForGivenView(c, v, v', step, h_v, h_step); HonestPreservesCommitMsgsFromHonestSendersAgree(c, v, v', step, h_v, h_step); HonestPreservesRecordedCheckpointsRecvdCameFromNetwork(c, v, v', step, h_v, h_step); - //TODO: extract into a lemma. - if(h_step.LeaveViewStep?) { - HonestLeaveViewStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); - } else if(h_step.SendPrepareStep?) { - HonestSendPrepareStepPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); - } else { - TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); - } - //END + HonestPreservesRecordedPrePreparesMatchHostView(c, v, v', step, h_v, h_step); + HonestPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); + HonestPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); } else { InvNextFaultyOrClient(c, v, v', step); } @@ -1278,41 +1386,55 @@ module Proof { assert EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v') by { AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v, v', step); } + assert RecordedPrePreparesMatchHostView(c, v') by { + reveal_RecordedPrePreparesMatchHostView(); + } + assert UnCommitableAgreesWithRecordedPrePrepare(c, v') by { + TriviallyPreserveUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, NotHonest()); + } assert UnCommitableAgreesWithPrepare(c, v') by { - var h_step :| true; - var h_v :| true; - TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, NotHonest()); } } + lemma InitEstablishesInv(c: Constants, v:Variables) + requires Init(c, v) + ensures Inv(c, v) + { + assert Inv(c, v) by { + reveal_AllReplicasLiteInv(); + reveal_RecordedCommitsClientOpsMatchPrePrepare(); + reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_HonestReplicasLockOnCommitForGivenView(); + reveal_CommitMsgsFromHonestSendersAgree(); + reveal_RecordedPreparesHaveValidSenderID(); + reveal_RecordedPrePreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesMatchHostView(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); + reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + reveal_HonestReplicasLockOnPrepareForGivenView(); + reveal_RecordedCheckpointsRecvdCameFromNetwork(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_RecordedPrePreparesMatchHostView(); + reveal_UnCommitableAgreesWithPrepare(); + reveal_UnCommitableAgreesWithRecordedPrePrepare(); + } + } + lemma InvariantInductive(c: Constants, v:Variables, v':Variables) ensures Init(c, v) ==> Inv(c, v) ensures Inv(c, v) && Next(c, v, v') ==> Inv(c, v') //ensures Inv(c, v) ==> Safety(c, v) { if Init(c, v) { - assert Inv(c, v) by { - reveal_AllReplicasLiteInv(); - reveal_RecordedCommitsClientOpsMatchPrePrepare(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); - reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - reveal_HonestReplicasLockOnCommitForGivenView(); - reveal_CommitMsgsFromHonestSendersAgree(); - reveal_RecordedPreparesHaveValidSenderID(); - reveal_RecordedPrePreparesRecvdCameFromNetwork(); - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); - reveal_RecordedPreparesMatchHostView(); - reveal_RecordedPreparesClientOpsMatchPrePrepare(); - reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); - reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - reveal_HonestReplicasLockOnPrepareForGivenView(); - reveal_RecordedCheckpointsRecvdCameFromNetwork(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); - } + InitEstablishesInv(c, v); } if Inv(c, v) && Next(c, v, v') { InvariantNext(c, v, v'); } } -} //Module \ No newline at end of file +} //Module From 8b87dc8f58eac4257903327fc0e40de9e20d8de4 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 16 Jan 2023 23:56:25 +0200 Subject: [PATCH 04/38] Additions to the global invariant. Added predicates HonestReplicasLeaveViewsBehind and RecordedNewViewMsgsContainSentVCMsgs to the global Invariant. Initial structuring for proving the Invarinat holds for HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare. --- docs/sbft-formal-model/proof.dfy | 102 ++++++++++++++++++++++++++----- 1 file changed, 86 insertions(+), 16 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 977b48514b..a4cb561ff1 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -309,6 +309,8 @@ module Proof { && RecordedCheckpointsRecvdCameFromNetwork(c, v) && UnCommitableAgreesWithPrepare(c, v) && UnCommitableAgreesWithRecordedPrePrepare(c, v) + && HonestReplicasLeaveViewsBehind(c, v) + && RecordedNewViewMsgsContainSentVCMsgs(c, v) } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, @@ -754,12 +756,17 @@ module Proof { requires match(info) { case HonestInfo(h_v, h_step) => (&& HonestReplicaStepTaken(c, v, v', step, h_v, h_step) && !h_step.RecvPrePrepareStep? - && !h_step.LeaveViewStep?) + && !h_step.LeaveViewStep? + && !h_step.SendPrePrepareStep?) case NotHonest() => (|| (NextStep(c, v, v', step) && c.clusterConfig.IsFaultyReplica(step.id)) || (NextStep(c, v, v', step) && c.clusterConfig.IsClient(step.id))) } ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') { + if info.HonestInfo? + { + info.h_v.workingWindow.reveal_Shift(); + } reveal_UnCommitableAgreesWithRecordedPrePrepare(); forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | && IsHonestReplica(c, replicaIdx) @@ -964,17 +971,17 @@ module Proof { { && c.clusterConfig.IsHonestReplica(replica) && Network.Message(replica, - Messages.Commit(view, - seqID, - operationWrapper)) in v.network.sentMsgs + Messages.Commit(view, + seqID, + operationWrapper)) in v.network.sentMsgs } predicate ReplicasInViewOrLower(c:Constants, - v:Variables, - seqID:Messages.SequenceID, - view:nat, - operationWrapper:Messages.OperationWrapper, - replica:HostIdentifiers.HostId) + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper, + replica:HostIdentifiers.HostId) requires v.WF(c) { && c.clusterConfig.IsHonestReplica(replica) @@ -1031,10 +1038,23 @@ module Proof { && priorView < prePrepareMsg.payload.view :: && var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; && UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper)) + } + + predicate {:opaque} HonestReplicasLeaveViewsBehind(c:Constants, v:Variables) { + && (forall viewChangeMsg | && viewChangeMsg in v.network.sentMsgs + && IsHonestReplica(c, viewChangeMsg.sender) + :: && var replicaVariables := v.hosts[viewChangeMsg.sender].replicaVariables; + && replicaVariables.view >= viewChangeMsg.payload.newView) + } + predicate {:opaque} RecordedNewViewMsgsContainSentVCMsgs(c:Constants, v:Variables) { + && (forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v.hosts[replicaIdx].replicaVariables; + && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs + :: newViewMsg.payload.vcMsgs.msgs <= v.network.sentMsgs) } - predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { + predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { && v.WF(c) && (forall observer, seqID, sender | && IsHonestReplica(c, observer) @@ -1265,16 +1285,66 @@ module Proof { reveal_UnCommitableAgreesWithRecordedPrePrepare(); forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | && IsHonestReplica(c, replicaIdx) - && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; - && var replicaConstants := c.hosts[replicaIdx].replicaConstants; - && seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) - && replicaVariables.workingWindow.prePreparesRcvd[seqID].Some? - && var prePrepareMsg := replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; + && var replicaConstants' := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables'.workingWindow.getActiveSequenceIDs(replicaConstants') + && replicaVariables'.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; && priorOperationWrapper != prePrepareMsg.payload.operationWrapper && priorView < prePrepareMsg.payload.view ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { - + var replicaVariables := v.hosts[replicaIdx].replicaVariables; + var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; + if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { + var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs + && msg.payload.newView == h_v.view; + if |newViewMsgs| == 0 { + assert h_v.view == 0; + assert false; + } + var newViewMsg :| newViewMsg in newViewMsgs; + var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); + var troubleMakers := ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper); + if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { + // Contradiction hypothesis + UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); + assert viewChangers <= getAllReplicas(c) by { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + assume false; // Need an invariant on the VC messages. + } + var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); + assert !c.clusterConfig.IsFaultyReplica(doubleAgent); + assert doubleAgent in viewChangers; + var vcMsg:Message :| && vcMsg in newViewMsg.payload.vcMsgs.msgs + && vcMsg.sender == doubleAgent; + assert vcMsg.payload.ViewChangeMsg?; + assert vcMsg in v.network.sentMsgs by { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + assume false; + } + assert !ReplicasThatSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + // Need to finish this proof + // reveal_SentViewChangesMsgsComportWithSentCommits(); + assume false; + } + assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + reveal_HonestReplicasLeaveViewsBehind(); + assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + } + assert doubleAgent !in viewChangers; + assert false; + } + var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + assert prePrepareMsg.payload.seqID == seqID; + assert |ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); + assert UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + } else { + var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; + assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + Library.SubsetCardinality(ReplicasThatCanCommitInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper), + ReplicasThatCanCommitInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper)); + } } } From e6c78960ffa50ecc91a655167bde35d17d8046b2 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 18 Jan 2023 14:22:57 +0200 Subject: [PATCH 05/38] Fix for sending PrePrepare. When sending a PrePrepare, the Primary has to check if the SeqID is from a previous view and oblige the necessary restrictions if this is the case. --- docs/sbft-formal-model/replica.i.dfy | 42 +++++++++++++++++++--------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 2d2e07b5b0..3192fcb4d3 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -258,7 +258,7 @@ module Replica { } function CalculateRestrictionForSeqID(c:Constants, v:Variables, seqID:SequenceID, newViewMsg:Network.Message) - : Option + : Option // returns None if any operation is allowed requires v.WF(c) requires newViewMsg.payload.NewViewMsg? requires newViewMsg.payload.vcMsgs.valid(v.view, c.clusterConfig.AgreementQuorum()) @@ -309,11 +309,8 @@ module Replica { && v.WF(c) && msgOps.IsSend() && CurrentPrimary(c, v) == c.myId - && seqID in v.workingWindow.getActiveSequenceIDs(c) - && msgOps.send == Some(Network.Message(c.myId, - PrePrepare(v.view, - seqID, - v.workingWindow.prePreparesRcvd[seqID].value.payload.operationWrapper))) + && IsValidPrePrepare(c, v, msgOps.send.value) + && msgOps.send.value.payload.seqID == seqID && v.workingWindow.prePreparesRcvd[seqID].None? && v' == v.(workingWindow := v.workingWindow.(prePreparesRcvd := @@ -330,8 +327,31 @@ module Replica { && PrimaryForView(c, newViewMsg.payload.newView) == newViewMsg.sender) } + predicate IsValidOperationWrapper(c:Constants, + v:Variables, + seqID:SequenceID, + newViewMsg:Network.Message, + operation:OperationWrapper) + requires v.WF(c) + requires newViewMsg.payload.NewViewMsg? + requires newViewMsg.payload.vcMsgs.valid(v.view, c.clusterConfig.AgreementQuorum()) + requires newViewMsg in v.newViewMsgsRecvd.msgs + // readability: + requires newViewMsg.payload.newView == v.view + requires CurrentPrimary(c, v) == newViewMsg.sender + requires seqID in v.workingWindow.getActiveSequenceIDs(c) + requires LiteInv(c, v) + { + && var restriction := CalculateRestrictionForSeqID(c, + v, + seqID, + newViewMsg); + && restriction.Some? ==> restriction.value == operation + } + + // For clarity here we have extracted all preconditions that must hold for a Replica to accept a PrePrepare - predicate IsValidPrePrepareToAccept(c:Constants, v:Variables, msg:Network.Message) + predicate IsValidPrePrepare(c:Constants, v:Variables, msg:Network.Message) { && v.WF(c) && LiteInv(c, v) @@ -348,11 +368,7 @@ module Replica { then true else && |newViewMsgs| == 1 && var newViewMsg :| newViewMsg in newViewMsgs; - && Some(msg.payload.operationWrapper) == CalculateRestrictionForSeqID(c, - v, - msg.payload.seqID, - newViewMsg)) - + && IsValidOperationWrapper(c, v, msg.payload.seqID, newViewMsg, msg.payload.operationWrapper)) } // Predicate that describes what is needed and how we mutate the state v into v' when RecvPrePrepare @@ -363,7 +379,7 @@ module Replica { && v.WF(c) && msgOps.IsRecv() && var msg := msgOps.recv.value; - && IsValidPrePrepareToAccept(c, v, msg) + && IsValidPrePrepare(c, v, msg) && v' == v.(workingWindow := v.workingWindow.(prePreparesRcvd := v.workingWindow.prePreparesRcvd[msg.payload.seqID := Some(msg)])) From 42f486bfbbde73b6f6cf3e9f524d9d1da500935d Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Fri, 20 Jan 2023 11:58:06 +0200 Subject: [PATCH 06/38] Added CurrentNewViewMsg predicate. To avoid code duplication we extract the prerequisites for a NewViewMsg to be the one for the current View a replica is in to a separate predicate. --- docs/sbft-formal-model/replica.i.dfy | 42 +++++++++++----------------- 1 file changed, 16 insertions(+), 26 deletions(-) diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 3192fcb4d3..5be5f3f6af 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -214,15 +214,7 @@ module Replica { } function ExtractSeqIDsFromPrevView(c:Constants, v:Variables, newViewMsg:Network.Message) : set - requires c.WF() - requires v.WF(c) - requires newViewMsg.payload.NewViewMsg? - requires newViewMsg.payload.valid(c.clusterConfig.AgreementQuorum()) - requires newViewMsg in v.newViewMsgsRecvd.msgs - // readability: - requires newViewMsg.payload.newView == v.view - requires CurrentPrimary(c, v) == newViewMsg.sender - requires LiteInv(c, v) + requires CurrentNewViewMsg(c, v, newViewMsg) { // 1. Check Each VC Msg for correct proof for last stable SeqID. // 2. Check New View for containing only correct VC msgs. @@ -259,15 +251,8 @@ module Replica { function CalculateRestrictionForSeqID(c:Constants, v:Variables, seqID:SequenceID, newViewMsg:Network.Message) : Option // returns None if any operation is allowed - requires v.WF(c) - requires newViewMsg.payload.NewViewMsg? - requires newViewMsg.payload.vcMsgs.valid(v.view, c.clusterConfig.AgreementQuorum()) - requires newViewMsg in v.newViewMsgsRecvd.msgs - // readability: - requires newViewMsg.payload.newView == v.view - requires CurrentPrimary(c, v) == newViewMsg.sender + requires CurrentNewViewMsg(c, v, newViewMsg) requires seqID in v.workingWindow.getActiveSequenceIDs(c) - requires LiteInv(c, v) { // 1. Take the NewViewMsg for the current View. // 2. Go through all the ViewChangeMsg-s in the NewView and take the valid full @@ -327,20 +312,26 @@ module Replica { && PrimaryForView(c, newViewMsg.payload.newView) == newViewMsg.sender) } + predicate CurrentNewViewMsg(c:Constants, + v:Variables, + newViewMsg:Network.Message) + { + && v.WF(c) + && LiteInv(c, v) + && newViewMsg.payload.NewViewMsg? + && newViewMsg.payload.valid(c.clusterConfig.AgreementQuorum()) + && newViewMsg in v.newViewMsgsRecvd.msgs + && newViewMsg.payload.newView == v.view + && CurrentPrimary(c, v) == newViewMsg.sender + } + predicate IsValidOperationWrapper(c:Constants, v:Variables, seqID:SequenceID, newViewMsg:Network.Message, operation:OperationWrapper) - requires v.WF(c) - requires newViewMsg.payload.NewViewMsg? - requires newViewMsg.payload.vcMsgs.valid(v.view, c.clusterConfig.AgreementQuorum()) - requires newViewMsg in v.newViewMsgsRecvd.msgs - // readability: - requires newViewMsg.payload.newView == v.view - requires CurrentPrimary(c, v) == newViewMsg.sender + requires CurrentNewViewMsg(c, v, newViewMsg) requires seqID in v.workingWindow.getActiveSequenceIDs(c) - requires LiteInv(c, v) { && var restriction := CalculateRestrictionForSeqID(c, v, @@ -349,7 +340,6 @@ module Replica { && restriction.Some? ==> restriction.value == operation } - // For clarity here we have extracted all preconditions that must hold for a Replica to accept a PrePrepare predicate IsValidPrePrepare(c:Constants, v:Variables, msg:Network.Message) { From 3829d981f0579cf0d2eb541d85b930e85970bf96 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Fri, 20 Jan 2023 12:16:24 +0200 Subject: [PATCH 07/38] Added RecordedViewChangeMsgsCameFromNetwork predicate to the global Invariant. We need to retain the information that the recorded ViewChangeMsgs in the replicas came from the network. Minor fixes and structuring of the code by adding the necessary proofs for the newly added predicates in the global Invariant - HonestReplicasLeaveViewsBehind, RecordedNewViewMsgsContainSentVCMsgs and RecordedViewChangeMsgsCameFromNetwork. --- docs/sbft-formal-model/proof.dfy | 95 +++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index a4cb561ff1..ca9c64e356 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -311,6 +311,7 @@ module Proof { && UnCommitableAgreesWithRecordedPrePrepare(c, v) && HonestReplicasLeaveViewsBehind(c, v) && RecordedNewViewMsgsContainSentVCMsgs(c, v) + && RecordedViewChangeMsgsCameFromNetwork(c, v) } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, @@ -1041,13 +1042,16 @@ module Proof { } predicate {:opaque} HonestReplicasLeaveViewsBehind(c:Constants, v:Variables) { + && v.WF(c) && (forall viewChangeMsg | && viewChangeMsg in v.network.sentMsgs + && viewChangeMsg.payload.ViewChangeMsg? && IsHonestReplica(c, viewChangeMsg.sender) :: && var replicaVariables := v.hosts[viewChangeMsg.sender].replicaVariables; && replicaVariables.view >= viewChangeMsg.payload.newView) } predicate {:opaque} RecordedNewViewMsgsContainSentVCMsgs(c:Constants, v:Variables) { + && v.WF(c) && (forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs @@ -1066,6 +1070,14 @@ module Proof { && replicaVars.view == replicaVars.workingWindow.prePreparesRcvd[seqID].value.payload.view) } + predicate {:opaque} RecordedViewChangeMsgsCameFromNetwork(c:Constants, v:Variables) { + && v.WF(c) + && (forall replicaIdx, viewChangeMsg | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v.hosts[replicaIdx].replicaVariables; + && viewChangeMsg in replicaVariables.viewChangeMsgsRecvd.msgs + :: viewChangeMsg in v.network.sentMsgs) + } //TODO: write a predicate Prepare matches Commit lemma GetPrepareFromHonestSenderForCommit(c:Constants, v:Variables, commitMsg:Message) @@ -1276,6 +1288,71 @@ module Proof { } } + lemma HonestPreservesHonestReplicasLeaveViewsBehind(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures HonestReplicasLeaveViewsBehind(c, v') + { + reveal_HonestReplicasLeaveViewsBehind(); + } + + lemma HonestPreservesRecordedNewViewMsgsContainSentVCMsgs(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures RecordedNewViewMsgsContainSentVCMsgs(c, v') + { + if(h_step.SelectQuorumOfViewChangeMsgsStep?) { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); + forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; + && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs + ensures newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs { + + if newViewMsg in v.hosts[replicaIdx].replicaVariables.newViewMsgsRecvd.msgs { + assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; + } else { + assert newViewMsg.payload.vcMsgs.msgs <= step.msgOps.signedMsgsToCheck; + assert step.msgOps.signedMsgsToCheck <= v.network.sentMsgs; + assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; + } + } + + assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); + } else if(h_step.RecvNewViewMsgStep?) { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); + } else { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); + } + + + } + + lemma HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures RecordedViewChangeMsgsCameFromNetwork(c, v') + { + reveal_RecordedViewChangeMsgsCameFromNetwork(); + forall replicaIdx, viewChangeMsg | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; + && viewChangeMsg in replicaVariables.viewChangeMsgsRecvd.msgs + ensures viewChangeMsg in v'.network.sentMsgs { + if viewChangeMsg !in v.network.sentMsgs { + if(h_step.RecvViewChangeMsgStep?) { + assert viewChangeMsg in v'.network.sentMsgs; + } else if(h_step.LeaveViewStep?){ + assert viewChangeMsg in v'.network.sentMsgs; + } else { + assert false; + } + } + } + } + lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1357,6 +1434,8 @@ module Proof { HonestLeaveViewStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); } else if(h_step.RecvPrePrepareStep?) { HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); + } else if(h_step.SendPrePrepareStep?) { + assume false; } else { TriviallyPreserveUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, HonestInfo(h_v, h_step)); } @@ -1393,6 +1472,9 @@ module Proof { HonestPreservesRecordedPrePreparesMatchHostView(c, v, v', step, h_v, h_step); HonestPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); + HonestPreservesHonestReplicasLeaveViewsBehind(c, v, v', step, h_v, h_step); + HonestPreservesRecordedNewViewMsgsContainSentVCMsgs(c, v, v', step, h_v, h_step); + HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c, v, v', step, h_v, h_step); } else { InvNextFaultyOrClient(c, v, v', step); } @@ -1465,7 +1547,15 @@ module Proof { assert UnCommitableAgreesWithPrepare(c, v') by { TriviallyPreserveUnCommitableAgreesWithPrepare(c, v, v', step, NotHonest()); } - + assert HonestReplicasLeaveViewsBehind(c, v') by { + reveal_HonestReplicasLeaveViewsBehind(); + } + assert RecordedNewViewMsgsContainSentVCMsgs(c, v') by { + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + } + assert RecordedViewChangeMsgsCameFromNetwork(c, v') by { + reveal_RecordedViewChangeMsgsCameFromNetwork(); + } } lemma InitEstablishesInv(c: Constants, v:Variables) @@ -1492,6 +1582,9 @@ module Proof { reveal_RecordedPrePreparesMatchHostView(); reveal_UnCommitableAgreesWithPrepare(); reveal_UnCommitableAgreesWithRecordedPrePrepare(); + reveal_HonestReplicasLeaveViewsBehind(); + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); } } From cb60947e481020adebfd21ce5e464d2501b92c51 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 25 Jan 2023 11:58:31 +0200 Subject: [PATCH 08/38] Additions to the replica model. * PreparedCertificate's valid method now takes a SeqID for whcich we check if the PreparedCertificate is valid. * LeaveView and SelectQuorumOfViewChangeMsgs send the newly created msgs right away. * On leaving a View we check for PreparedCertificates in both the Working Window and in the latest View Change msg we have generated. --- docs/sbft-formal-model/messages.dfy | 3 +- docs/sbft-formal-model/replica.i.dfy | 60 ++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index c6a919348c..4194fe1b57 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -40,10 +40,11 @@ module Messages { predicate WF() { (forall v | v in votes :: v.payload.Prepare?) } - predicate valid(quorumSize:nat) { + predicate valid(quorumSize:nat, seqID:SequenceID) { || empty() || (&& |votes| == quorumSize && WF() + && prototype().seqID == seqID && (forall v | v in votes :: v.payload == prototype()) // messages have to be votes that match eachother by the prototype && UniqueSenders(votes)) } diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 5be5f3f6af..e34060e597 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -521,7 +521,7 @@ module Replica { predicate LeaveView(c:Constants, v:Variables, v':Variables, msgOps:Network.MessageOps, newView:ViewNum) { // TODO: Clear all Working Window after we leave a View. && v.WF(c) - && msgOps.NoSendRecv() + && msgOps.IsSend() // We can only leave a view we have collected at least 2F+1 View // Change messages for in viewChangeMsgsRecvd or View is 0. && (|| (HasCollectedProofMyViewIsAgreed(c, v) && newView == v.view + 1) @@ -538,6 +538,7 @@ module Replica { && v' == v.(view := newView) .(viewChangeMsgsRecvd := v.viewChangeMsgsRecvd.(msgs := v.viewChangeMsgsRecvd.msgs + {vcMsg})) .(workingWindow := v.workingWindow.EmptyWorkingWindow(c)) + && msgOps.send.value == vcMsg } function ExtractStableCheckpointProof(c:Constants, v:Variables) : set> @@ -550,6 +551,32 @@ module Replica { && msg.payload.committedClientOperations == stateUpToSeqID } + predicate IsRecordedViewChangeMsgForView(c:Constants, v:Variables, view:ViewNum, viewChangeMsg:Network.Message) + { + && v.WF(c) + && viewChangeMsg.payload.ViewChangeMsg? + && viewChangeMsg in v.viewChangeMsgsRecvd.msgs + && viewChangeMsg.payload.newView == view + && viewChangeMsg.sender == c.myId + } + + function GetViewChangeMsgForView(c:Constants, v:Variables, view:ViewNum) : Network.Message + requires v.WF(c) + requires exists viewChangeMsg :: IsRecordedViewChangeMsgForView(c, v, view, viewChangeMsg) + { + var viewChangeMsg :| IsRecordedViewChangeMsgForView(c, v, view, viewChangeMsg); + viewChangeMsg + } + + function ExtractPreparedCertificateFromLatestViewChangeMsg(c:Constants, v:Variables, seqID:SequenceID) : PreparedCertificate + { + if !exists vcMsg :: IsRecordedViewChangeMsgForView(c, v, v.view, vcMsg) + then PreparedCertificate({}) + else if seqID !in GetViewChangeMsgForView(c, v, v.view).payload.certificates + then PreparedCertificate({}) + else GetViewChangeMsgForView(c, v, v.view).payload.certificates[seqID] + } + function ExtractCertificatesFromWorkingWindow(c:Constants, v:Variables) : map requires v.WF(c) { @@ -560,10 +587,13 @@ module Replica { requires v.WF(c) requires seqID in v.workingWindow.getActiveSequenceIDs(c) { - var preparesRecvd := set msg | msg in v.workingWindow.preparesRcvd[seqID].Values && msg.payload.Prepare?; - if |preparesRecvd| < c.clusterConfig.AgreementQuorum() - then PreparedCertificate({}) - else PreparedCertificate(preparesRecvd) + var workingWindowPreparesRecvd := v.workingWindow.preparesRcvd[seqID].Values; + var viewChangeMsgPreparesRecvd := ExtractPreparedCertificateFromLatestViewChangeMsg(c, v, seqID).votes; + if |workingWindowPreparesRecvd| >= c.clusterConfig.AgreementQuorum() + then PreparedCertificate(workingWindowPreparesRecvd) + else if |viewChangeMsgPreparesRecvd| >= c.clusterConfig.AgreementQuorum() + then PreparedCertificate(viewChangeMsgPreparesRecvd) + else PreparedCertificate({}) } predicate SendViewChangeMsg(c:Constants, v:Variables, v':Variables, msgOps:Network.MessageOps) @@ -584,7 +614,7 @@ module Replica { msgOps:Network.MessageOps, viewChangeMsgsSelectedByPrimary:ViewChangeMsgsSelectedByPrimary) { && v.WF(c) - && msgOps.NoSendRecv() + && msgOps.IsSend() && CurrentPrimary(c, v) == c.myId && (forall msg | && msg in v.newViewMsgsRecvd.msgs && msg.sender == c.myId @@ -595,6 +625,7 @@ module Replica { && var newViewMsg := Network.Message(c.myId, NewViewMsg(v.view, viewChangeMsgsSelectedByPrimary)); && v' == v.(newViewMsgsRecvd := v.newViewMsgsRecvd.(msgs := v.newViewMsgsRecvd.msgs + {newViewMsg})) + && msgOps.send.value == newViewMsg } predicate SendNewViewMsg(c:Constants, v:Variables, v':Variables, msgOps:Network.MessageOps) @@ -609,6 +640,21 @@ module Replica { && v' == v } + predicate ValidViewChangeMsg(msg:Network.Message, + networkMsgs:set>, + agreementQuorum:nat) + { + && msg.payload.ViewChangeMsg? + // Check Checkpoint msg-s signatures: + && var checkpointMsgs := set c | c in msg.payload.proofForLastStable.msgs; + && checkpointMsgs <= networkMsgs + // Check Signatures for the Prepared Certificates: + && (forall seqID | seqID in msg.payload.certificates + :: && msg.payload.certificates[seqID].votes <= networkMsgs + && msg.payload.certificates[seqID].valid(agreementQuorum, seqID)) // TODO: refactor to put this in msg.payload.valid(agreementQuorum) + && msg.payload.valid(agreementQuorum) + } + predicate RecvViewChangeMsg(c:Constants, v:Variables, v':Variables, msgOps:Network.MessageOps) { && v.WF(c) @@ -621,7 +667,7 @@ module Replica { // Check Signatures for the Prepared Certificates: && (forall seqID | seqID in msg.payload.certificates :: && msg.payload.certificates[seqID].votes <= msgOps.signedMsgsToCheck - && msg.payload.certificates[seqID].valid(c.clusterConfig.AgreementQuorum())) + && msg.payload.certificates[seqID].valid(c.clusterConfig.AgreementQuorum(), seqID)) && msg.payload.valid(c.clusterConfig.AgreementQuorum()) && v' == v.(viewChangeMsgsRecvd := v.viewChangeMsgsRecvd.(msgs := v.viewChangeMsgsRecvd.msgs + {msg})) } From 6610140c6f314b3ef4a2fc272ee84a10de2ab24a Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 25 Jan 2023 12:49:20 +0200 Subject: [PATCH 09/38] Added predicates to global Invariant. * Predicate EveryCommitMsgIsRememberedByItsSender - temporary predicate states the honest replicas remember their Commit msgs. This will be revised Once Checkpoint and Shift are introduced to the proof. * RecordedViewChangeMsgsAreValid - predicate indicating that honest replicas validate a View Change msg before recording it, therefore all VC msgs an honest replica has recorded are valid. * SentViewChangesMsgsComportWithSentCommits - predicate indicating that honest replicas insert the prepared certificates they have collected into the View Change messages they have generated. * Cleanup of debugging code in the proof. --- docs/sbft-formal-model/proof.dfy | 174 ++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 60 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index ca9c64e356..69aabc8a7c 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -83,6 +83,29 @@ module Proof { commitMsg.payload.seqID, commitMsg.payload.operationWrapper) ) } + predicate {:opaque} EveryCommitMsgIsRememberedByItsSender(c:Constants, v:Variables) { //TODO: this does not cover Checkpointing + && v.WF(c) + && (forall commitMsg | && commitMsg in v.network.sentMsgs + && commitMsg.payload.Commit? + && IsHonestReplica(c, commitMsg.sender) + && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v := v.hosts[commitMsg.sender].replicaVariables; + && commitMsg.payload.seqID in h_v.workingWindow.getActiveSequenceIDs(h_c) //TODO: remove when Checkpointing gets enabled + :: && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v := v.hosts[commitMsg.sender].replicaVariables; + && var certificate := Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID); + && certificate.valid(c.clusterConfig.AgreementQuorum(), commitMsg.payload.seqID) + && !certificate.empty() + && certificate.prototype().view == commitMsg.payload.view + && certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + } + + predicate {:opaque} TemporarilyDisableCheckpointing(c:Constants, v:Variables) { + && v.WF(c) + && (forall replicaID | && IsHonestReplica(c, replicaID) + :: v.hosts[replicaID].replicaVariables.workingWindow.lastStableCheckpoint == 0) + } + // This predicate states that honest replicas accept the first PrePrepare they receive and vote // with a Prepare message only for it. The lock on Prepare is meant to highlight the fact that // even though a replica can send multiple times a Prepare message for a given Sequence ID for @@ -312,6 +335,11 @@ module Proof { && HonestReplicasLeaveViewsBehind(c, v) && RecordedNewViewMsgsContainSentVCMsgs(c, v) && RecordedViewChangeMsgsCameFromNetwork(c, v) + && SentViewChangesMsgsComportWithSentCommits(c, v) + && EveryCommitMsgIsRememberedByItsSender(c, v) + && TemporarilyDisableCheckpointing(c, v) + // AllPreparedCertsInWorkingWindowAreValid + // AllPreparedCertsInViewChangeMsgsAreValid } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, @@ -1050,12 +1078,18 @@ module Proof { && replicaVariables.view >= viewChangeMsg.payload.newView) } + predicate MessagesAreFromReplicas(c:Constants, msgs:set) { + && c.WF() + && (forall msg | msg in msgs :: c.clusterConfig.IsReplica(msg.sender)) + } + predicate {:opaque} RecordedNewViewMsgsContainSentVCMsgs(c:Constants, v:Variables) { && v.WF(c) && (forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs - :: newViewMsg.payload.vcMsgs.msgs <= v.network.sentMsgs) + :: && newViewMsg.payload.vcMsgs.msgs <= v.network.sentMsgs + && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs)) } predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { @@ -1079,6 +1113,37 @@ module Proof { :: viewChangeMsg in v.network.sentMsgs) } + predicate {:opaque} RecordedViewChangeMsgsAreValid(c:Constants, v:Variables) { + && v.WF(c) + && (forall replicaIdx, viewChangeMsg | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables := v.hosts[replicaIdx].replicaVariables; + && viewChangeMsg in replicaVariables.viewChangeMsgsRecvd.msgs + :: Replica.ValidViewChangeMsg(viewChangeMsg, + v.network.sentMsgs, + c.clusterConfig.AgreementQuorum())) + } + + predicate {:opaque} SentViewChangesMsgsComportWithSentCommits(c:Constants, v:Variables) { + && v.WF(c) + && (forall viewChangeMsg, commitMsg | + && viewChangeMsg in v.network.sentMsgs + && commitMsg in v.network.sentMsgs + && viewChangeMsg.payload.ViewChangeMsg? + && commitMsg.payload.Commit? + && commitMsg.payload.view <= viewChangeMsg.payload.newView + && commitMsg.sender == viewChangeMsg.sender + //TODO: add Shift consequences (this works only if no one advances the WW) + && IsHonestReplica(c, viewChangeMsg.sender) + :: && commitMsg.payload.seqID in viewChangeMsg.payload.certificates + && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; + && certificate.valid(c.clusterConfig.AgreementQuorum(), commitMsg.payload.seqID) + && !certificate.empty() + && certificate.prototype().view >= commitMsg.payload.view + // && certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper + ) + } + //TODO: write a predicate Prepare matches Commit lemma GetPrepareFromHonestSenderForCommit(c:Constants, v:Variables, commitMsg:Message) returns (prepare:Message) @@ -1301,33 +1366,8 @@ module Proof { requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) ensures RecordedNewViewMsgsContainSentVCMsgs(c, v') { - if(h_step.SelectQuorumOfViewChangeMsgsStep?) { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); - reveal_RecordedViewChangeMsgsCameFromNetwork(); - forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) - && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; - && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs - ensures newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs { - - if newViewMsg in v.hosts[replicaIdx].replicaVariables.newViewMsgsRecvd.msgs { - assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; - } else { - assert newViewMsg.payload.vcMsgs.msgs <= step.msgOps.signedMsgsToCheck; - assert step.msgOps.signedMsgsToCheck <= v.network.sentMsgs; - assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; - } - } - - assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); - } else if(h_step.RecvNewViewMsgStep?) { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); - assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); - } else { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); - assert RecordedNewViewMsgsContainSentVCMsgs(c, v'); - } - - + reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); } lemma HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1336,21 +1376,6 @@ module Proof { ensures RecordedViewChangeMsgsCameFromNetwork(c, v') { reveal_RecordedViewChangeMsgsCameFromNetwork(); - forall replicaIdx, viewChangeMsg | - && IsHonestReplica(c, replicaIdx) - && var replicaVariables := v'.hosts[replicaIdx].replicaVariables; - && viewChangeMsg in replicaVariables.viewChangeMsgsRecvd.msgs - ensures viewChangeMsg in v'.network.sentMsgs { - if viewChangeMsg !in v.network.sentMsgs { - if(h_step.RecvViewChangeMsgStep?) { - assert viewChangeMsg in v'.network.sentMsgs; - } else if(h_step.LeaveViewStep?){ - assert viewChangeMsg in v'.network.sentMsgs; - } else { - assert false; - } - } - } } lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1361,21 +1386,21 @@ module Proof { { reveal_UnCommitableAgreesWithRecordedPrePrepare(); forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | - && IsHonestReplica(c, replicaIdx) - && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; - && var replicaConstants' := c.hosts[replicaIdx].replicaConstants; - && seqID in replicaVariables'.workingWindow.getActiveSequenceIDs(replicaConstants') - && replicaVariables'.workingWindow.prePreparesRcvd[seqID].Some? - && var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; - && priorOperationWrapper != prePrepareMsg.payload.operationWrapper - && priorView < prePrepareMsg.payload.view - ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; - && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { + && IsHonestReplica(c, replicaIdx) + && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; + && var replicaConstants' := c.hosts[replicaIdx].replicaConstants; + && seqID in replicaVariables'.workingWindow.getActiveSequenceIDs(replicaConstants') + && replicaVariables'.workingWindow.prePreparesRcvd[seqID].Some? + && var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; + && priorOperationWrapper != prePrepareMsg.payload.operationWrapper + && priorView < prePrepareMsg.payload.view + ensures && var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { var replicaVariables := v.hosts[replicaIdx].replicaVariables; var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; - if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { - var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs - && msg.payload.newView == h_v.view; + if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { // Interesting case is when we record a new PrePrepare. + var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs // The checks that the Replica does before recording + && msg.payload.newView == h_v.view; // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare if |newViewMsgs| == 0 { assert h_v.view == 0; assert false; @@ -1388,7 +1413,7 @@ module Proof { UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); assert viewChangers <= getAllReplicas(c) by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); - assume false; // Need an invariant on the VC messages. + //assume false; // Need an invariant on the VC messages. } var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); assert !c.clusterConfig.IsFaultyReplica(doubleAgent); @@ -1398,12 +1423,12 @@ module Proof { assert vcMsg.payload.ViewChangeMsg?; assert vcMsg in v.network.sentMsgs by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); - assume false; + //assume false; } assert !ReplicasThatSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // Need to finish this proof - // reveal_SentViewChangesMsgsComportWithSentCommits(); - assume false; + reveal_SentViewChangesMsgsComportWithSentCommits(); + //assume false; } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { reveal_HonestReplicasLeaveViewsBehind(); @@ -1441,6 +1466,22 @@ module Proof { } } + lemma HonestPreservesSentViewChangesMsgsComportWithSentCommits(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures SentViewChangesMsgsComportWithSentCommits(c, v') + { + reveal_SentViewChangesMsgsComportWithSentCommits(); + } + + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSender(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures EveryCommitMsgIsRememberedByItsSender(c, v') + { + reveal_EveryCommitMsgIsRememberedByItsSender(); + } + lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') @@ -1475,6 +1516,9 @@ module Proof { HonestPreservesHonestReplicasLeaveViewsBehind(c, v, v', step, h_v, h_step); HonestPreservesRecordedNewViewMsgsContainSentVCMsgs(c, v, v', step, h_v, h_step); HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c, v, v', step, h_v, h_step); + HonestPreservesSentViewChangesMsgsComportWithSentCommits(c, v, v', step, h_v, h_step); + HonestPreservesEveryCommitMsgIsRememberedByItsSender(c, v, v', step, h_v, h_step); + assume TemporarilyDisableCheckpointing(c, v'); } else { InvNextFaultyOrClient(c, v, v', step); } @@ -1556,6 +1600,13 @@ module Proof { assert RecordedViewChangeMsgsCameFromNetwork(c, v') by { reveal_RecordedViewChangeMsgsCameFromNetwork(); } + assert SentViewChangesMsgsComportWithSentCommits(c, v') by { + reveal_SentViewChangesMsgsComportWithSentCommits(); + } + assert EveryCommitMsgIsRememberedByItsSender(c, v') by { + reveal_EveryCommitMsgIsRememberedByItsSender(); + } + assume TemporarilyDisableCheckpointing(c, v'); } lemma InitEstablishesInv(c: Constants, v:Variables) @@ -1585,6 +1636,9 @@ module Proof { reveal_HonestReplicasLeaveViewsBehind(); reveal_RecordedNewViewMsgsContainSentVCMsgs(); reveal_RecordedViewChangeMsgsCameFromNetwork(); + reveal_SentViewChangesMsgsComportWithSentCommits(); + reveal_EveryCommitMsgIsRememberedByItsSender(); + assume TemporarilyDisableCheckpointing(c, v); } } From 77fb0bc84313b14ea8dfb32ebd62df552dec64e9 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 30 Jan 2023 14:43:26 +0200 Subject: [PATCH 10/38] Fixes regarding indexing of Stable Checkpoints. * SequenceID data type starts form 0, but we start committing from 1. This way we start with LastStableCkeckpoint at 0 and commit beyond it. * Fix for CheckpointsQuorum's valid predicate to take in consideration that for Sequence ID 0 we don't need agreement quorum of Checkpoint messages. * Added checked predicate for ViewChangeMsg and NewViewMsg which does all necessary checks including the signature checking, which is achieved on the call site by passing all the messages the network has stored. The call sites in replica.i.dfy have been revised. * Fix to set the lastStableCheckpoint == 0 in replica.i.dfy's Init predicate. * proof.dfy has been revised to take in consideration the above changes + hints on the lemmas that need to be completed. --- docs/sbft-formal-model/messages.dfy | 46 +++++++-- docs/sbft-formal-model/proof.dfy | 140 +++++++++++++++++++++------ docs/sbft-formal-model/replica.i.dfy | 41 +++----- 3 files changed, 165 insertions(+), 62 deletions(-) diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index 4194fe1b57..627cf85cb3 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -11,13 +11,15 @@ include "network.s.dfy" include "library/Library.dfy" +include "cluster_config.s.dfy" module Messages { import Library import opened HostIdentifiers import Network + import ClusterConfig - type SequenceID = k:nat | 0 < k witness 1 + type SequenceID = nat type ViewNum = nat datatype ClientOperation = ClientOperation(sender:HostId, timestamp:nat) @@ -72,12 +74,14 @@ module Messages { prot.payload } predicate valid(lastStableCheckpoint:SequenceID, quorumSize:nat) { - && |msgs| > 0 - && UniqueSenders(msgs) - && (forall m | m in msgs :: && m.payload.CheckpointMsg? - && m.payload == prototype() - && m.payload.seqIDReached == lastStableCheckpoint) - && |msgs| >= quorumSize + || (&& lastStableCheckpoint == 0 + && |msgs| == 0) + || (&& |msgs| > 0 + && UniqueSenders(msgs) + && (forall m | m in msgs :: && m.payload.CheckpointMsg? + && m.payload == prototype() + && m.payload.seqIDReached == lastStableCheckpoint) + && |msgs| >= quorumSize) } } @@ -105,6 +109,12 @@ module Messages { && (ViewChangeMsg? ==> validViewChangeMsg(quorumSize)) && (NewViewMsg? ==> validNewViewMsg(quorumSize)) } + predicate checked(clusterConfig:ClusterConfig.Constants, sigChecks:set>) + requires clusterConfig.WF() + { + && (ViewChangeMsg? ==> checkedViewChangeMsg(clusterConfig, sigChecks)) + && (NewViewMsg? ==> checkedNewViewMsg(clusterConfig, sigChecks)) + } predicate validViewChangeMsg(quorumSize:nat) requires ViewChangeMsg? { @@ -115,6 +125,28 @@ module Messages { { vcMsgs.valid(newView, quorumSize) } + predicate checkedViewChangeMsg(clusterConfig:ClusterConfig.Constants, sigChecks:set>) + requires ViewChangeMsg? + requires clusterConfig.WF() + { + && valid(clusterConfig.AgreementQuorum()) + // Check Checkpoint msg-s signatures: + && proofForLastStable.msgs <= sigChecks + && (forall seqID | seqID in certificates + :: && certificates[seqID].votes <= sigChecks + && (forall replica | replica in sendersOf(certificates[seqID].votes) + :: clusterConfig.IsReplica(replica)) + && certificates[seqID].valid(clusterConfig.AgreementQuorum(), seqID)) + } + predicate checkedNewViewMsg(clusterConfig:ClusterConfig.Constants, sigChecks:set>) + requires NewViewMsg? + requires clusterConfig.WF() + { + && valid(clusterConfig.AgreementQuorum()) + && (forall replica | replica in sendersOf(vcMsgs.msgs) + :: clusterConfig.IsReplica(replica)) + && vcMsgs.msgs <= sigChecks + } predicate IsIntraViewMsg() { || PrePrepare? || Prepare? diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 69aabc8a7c..b614d03b5c 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -52,7 +52,7 @@ module Proof { } - predicate RecordedPreparesRecvdCameFromNetwork(c:Constants, v:Variables, observer:HostId) + predicate RecordedPreparesRecvdCameFromNetworkForHost(c:Constants, v:Variables, observer:HostId) { && v.WF(c) && IsHonestReplica(c, observer) @@ -67,11 +67,11 @@ module Proof { && msg.payload.seqID == seqID)) // The key we stored matches what is in the msg } - predicate {:opaque} RecordedPreparesInAllHostsRecvdCameFromNetwork(c:Constants, v:Variables) { + predicate {:opaque} RecordedPreparesRecvdCameFromNetwork(c:Constants, v:Variables) { && v.WF(c) && (forall observer | && IsHonestReplica(c, observer) - :: RecordedPreparesRecvdCameFromNetwork(c, v, observer)) + :: RecordedPreparesRecvdCameFromNetworkForHost(c, v, observer)) } predicate {:opaque} EveryCommitMsgIsSupportedByAQuorumOfPrepares(c:Constants, v:Variables) { @@ -96,8 +96,10 @@ module Proof { && var certificate := Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID); && certificate.valid(c.clusterConfig.AgreementQuorum(), commitMsg.payload.seqID) && !certificate.empty() - && certificate.prototype().view == commitMsg.payload.view - && certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + && certificate.prototype().view >= commitMsg.payload.view + // && (certificate.prototype().view == commitMsg.payload.view + // ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + ) } predicate {:opaque} TemporarilyDisableCheckpointing(c:Constants, v:Variables) { @@ -207,7 +209,7 @@ module Proof { && IsHonestReplica(c, msg.sender) :: && var replicaVariables := v.hosts[msg.sender].replicaVariables; && var replicaConstants := c.hosts[msg.sender].replicaConstants; - && msg.payload.seqID < replicaVariables.workingWindow.lastStableCheckpoint + replicaConstants.clusterConfig.workingWindowSize) + && msg.payload.seqID <= replicaVariables.workingWindow.lastStableCheckpoint + replicaConstants.clusterConfig.workingWindowSize) } predicate {:opaque} EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(c:Constants, v:Variables) { @@ -316,7 +318,7 @@ module Proof { && RecordedPreparesHaveValidSenderID(c, v) //&& SentPreparesMatchRecordedPrePrepareIfHostInSameView(c, v) && RecordedPrePreparesRecvdCameFromNetwork(c, v) - && RecordedPreparesInAllHostsRecvdCameFromNetwork(c, v) + && RecordedPreparesRecvdCameFromNetwork(c, v) && RecordedPrePreparesMatchHostView(c, v) && RecordedPreparesMatchHostView(c, v) && EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v) @@ -331,15 +333,15 @@ module Proof { && CommitMsgsFromHonestSendersAgree(c, v) && RecordedCheckpointsRecvdCameFromNetwork(c, v) && UnCommitableAgreesWithPrepare(c, v) - && UnCommitableAgreesWithRecordedPrePrepare(c, v) + && UnCommitableAgreesWithRecordedPrePrepare(c, v)//Unfinished proof. && HonestReplicasLeaveViewsBehind(c, v) && RecordedNewViewMsgsContainSentVCMsgs(c, v) && RecordedViewChangeMsgsCameFromNetwork(c, v) - && SentViewChangesMsgsComportWithSentCommits(c, v) - && EveryCommitMsgIsRememberedByItsSender(c, v) + && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. + && EveryCommitMsgIsRememberedByItsSender(c, v)//Unfinished proof. + && RecordedViewChangeMsgsAreValid(c, v)//Unfinished proof. && TemporarilyDisableCheckpointing(c, v) - // AllPreparedCertsInWorkingWindowAreValid - // AllPreparedCertsInViewChangeMsgsAreValid + // "AllPreparedCertsInWorkingWindowAreValid" } function sentPreparesForSeqID(c: Constants, v:Variables, view:nat, seqID:Messages.SequenceID, @@ -378,7 +380,7 @@ module Proof { requires IsHonestReplica(c, msg2.sender) ensures msg1.payload.operationWrapper == msg2.payload.operationWrapper { - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesRecvdCameFromNetwork(); reveal_RecordedPreparesMatchHostView(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); reveal_HonestReplicasLockOnPrepareForGivenView(); @@ -539,12 +541,12 @@ module Proof { } } - lemma HonestPreservesRecordedPreparesInAllHostsRecvdCameFromNetwork(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + lemma HonestPreservesRecordedPreparesRecvdCameFromNetwork(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) - ensures RecordedPreparesInAllHostsRecvdCameFromNetwork(c, v') + ensures RecordedPreparesRecvdCameFromNetwork(c, v') { - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesRecvdCameFromNetwork(); if (h_step.AdvanceWorkingWindowStep?) { h_v.workingWindow.reveal_Shift(); @@ -587,7 +589,7 @@ module Proof { commitMsg.payload.seqID, commitMsg.payload.operationWrapper)); Library.SubsetCardinality(senders, senders'); } else { // the commitMsg is being sent in the current step - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesRecvdCameFromNetwork(); reveal_RecordedPreparesMatchHostView(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); var prepares := sentPreparesForSeqID(c, v, commitMsg.payload.view, @@ -878,7 +880,7 @@ module Proof { // reveal_AllReplicasLiteInv(); // reveal_RecordedPreparesHaveValidSenderID(); reveal_RecordedPrePreparesRecvdCameFromNetwork(); - // reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + // reveal_RecordedPreparesRecvdCameFromNetwork(); // reveal_RecordedPreparesMatchHostView(); // reveal_RecordedPreparesClientOpsMatchPrePrepare(); // reveal_RecordedCommitsClientOpsMatchPrePrepare(); @@ -1119,9 +1121,10 @@ module Proof { && IsHonestReplica(c, replicaIdx) && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && viewChangeMsg in replicaVariables.viewChangeMsgsRecvd.msgs - :: Replica.ValidViewChangeMsg(viewChangeMsg, - v.network.sentMsgs, - c.clusterConfig.AgreementQuorum())) + :: && var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && Replica.ValidViewChangeMsg(replicaConstants, + viewChangeMsg, + v.network.sentMsgs)) } predicate {:opaque} SentViewChangesMsgsComportWithSentCommits(c:Constants, v:Variables) { @@ -1368,6 +1371,30 @@ module Proof { { reveal_RecordedNewViewMsgsContainSentVCMsgs(); reveal_RecordedViewChangeMsgsCameFromNetwork(); + + forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) + && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; + && newViewMsg in replicaVariables'.newViewMsgsRecvd.msgs + ensures + && newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs + && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs) + { + var replicaVariables := v.hosts[replicaIdx].replicaVariables; + if (newViewMsg in replicaVariables.newViewMsgsRecvd.msgs) { + assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; + assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); + } else { + if (h_step.RecvNewViewMsgStep?) { + assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; + assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); + } else if (h_step.SelectQuorumOfViewChangeMsgsStep?) { + assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; + assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); + } else { + assert false; + } + } + } } lemma HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1428,7 +1455,7 @@ module Proof { assert !ReplicasThatSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // Need to finish this proof reveal_SentViewChangesMsgsComportWithSentCommits(); - //assume false; + assume false; } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { reveal_HonestReplicasLeaveViewsBehind(); @@ -1472,6 +1499,7 @@ module Proof { ensures SentViewChangesMsgsComportWithSentCommits(c, v') { reveal_SentViewChangesMsgsComportWithSentCommits(); + assume false; } lemma HonestPreservesEveryCommitMsgIsRememberedByItsSender(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1480,8 +1508,59 @@ module Proof { ensures EveryCommitMsgIsRememberedByItsSender(c, v') { reveal_EveryCommitMsgIsRememberedByItsSender(); - } + // reveal_AllReplicasLiteInv(); + // reveal_RecordedPreparesHaveValidSenderID(); + // reveal_RecordedPrePreparesRecvdCameFromNetwork(); + // reveal_RecordedPreparesRecvdCameFromNetwork(); + // reveal_RecordedPrePreparesMatchHostView(); + // reveal_RecordedPreparesMatchHostView(); + // reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + // reveal_RecordedPreparesClientOpsMatchPrePrepare(); + // reveal_RecordedCommitsClientOpsMatchPrePrepare(); + // reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + // reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + // reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + // reveal_HonestReplicasLockOnPrepareForGivenView(); + // reveal_HonestReplicasLockOnCommitForGivenView(); + // reveal_CommitMsgsFromHonestSendersAgree(); + // reveal_RecordedCheckpointsRecvdCameFromNetwork(); + // reveal_UnCommitableAgreesWithPrepare(); + // reveal_UnCommitableAgreesWithRecordedPrePrepare();//Unfinished proof. + // reveal_HonestReplicasLeaveViewsBehind(); + // reveal_RecordedNewViewMsgsContainSentVCMsgs(); + // reveal_RecordedViewChangeMsgsCameFromNetwork(); + // reveal_SentViewChangesMsgsComportWithSentCommits();//Unfinished proof. + // reveal_EveryCommitMsgIsRememberedByItsSender();//Unfinished proof. + // reveal_RecordedViewChangeMsgsAreValid();//Unfinished proof. + // reveal_TemporarilyDisableCheckpointing(); + } + + lemma HonestPreservesRecordedViewChangeMsgsAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures RecordedViewChangeMsgsAreValid(c, v') + { + reveal_RecordedViewChangeMsgsAreValid(); + reveal_TemporarilyDisableCheckpointing(); // Needed for LeaveViewStep + if (h_step.LeaveViewStep?) { + reveal_RecordedPreparesHaveValidSenderID(); + reveal_RecordedPrePreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesRecvdCameFromNetwork(); + reveal_RecordedPrePreparesMatchHostView(); + reveal_RecordedPreparesMatchHostView(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); + reveal_RecordedCommitsClientOpsMatchPrePrepare(); + reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_HonestReplicasLockOnPrepareForGivenView(); + assume false; + } + } lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') @@ -1497,7 +1576,7 @@ module Proof { HonestPreservesAllReplicasLiteInv(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesHaveValidSenderID(c, v, v', step, h_v, h_step); HonestPreservesRecordedPrePreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); - HonestPreservesRecordedPreparesInAllHostsRecvdCameFromNetwork(c, v, v', step, h_v, h_step); + HonestPreservesRecordedPreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesMatchHostView(c, v, v', step, h_v, h_step); AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v, v', step); HonestPreservesRecordedPreparesClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); @@ -1518,6 +1597,7 @@ module Proof { HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesSentViewChangesMsgsComportWithSentCommits(c, v, v', step, h_v, h_step); HonestPreservesEveryCommitMsgIsRememberedByItsSender(c, v, v', step, h_v, h_step); + HonestPreservesRecordedViewChangeMsgsAreValid(c, v, v', step, h_v, h_step); assume TemporarilyDisableCheckpointing(c, v'); } else { InvNextFaultyOrClient(c, v, v', step); @@ -1540,8 +1620,8 @@ module Proof { assert RecordedPrePreparesRecvdCameFromNetwork(c, v') by { reveal_RecordedPrePreparesRecvdCameFromNetwork(); } - assert RecordedPreparesInAllHostsRecvdCameFromNetwork(c, v') by { - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + assert RecordedPreparesRecvdCameFromNetwork(c, v') by { + reveal_RecordedPreparesRecvdCameFromNetwork(); } assert RecordedPreparesMatchHostView(c, v') by { reveal_RecordedPreparesMatchHostView(); @@ -1606,6 +1686,9 @@ module Proof { assert EveryCommitMsgIsRememberedByItsSender(c, v') by { reveal_EveryCommitMsgIsRememberedByItsSender(); } + assert RecordedViewChangeMsgsAreValid(c, v') by { + reveal_RecordedViewChangeMsgsAreValid(); + } assume TemporarilyDisableCheckpointing(c, v'); } @@ -1622,7 +1705,7 @@ module Proof { reveal_CommitMsgsFromHonestSendersAgree(); reveal_RecordedPreparesHaveValidSenderID(); reveal_RecordedPrePreparesRecvdCameFromNetwork(); - reveal_RecordedPreparesInAllHostsRecvdCameFromNetwork(); + reveal_RecordedPreparesRecvdCameFromNetwork(); reveal_RecordedPreparesMatchHostView(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); @@ -1638,7 +1721,8 @@ module Proof { reveal_RecordedViewChangeMsgsCameFromNetwork(); reveal_SentViewChangesMsgsComportWithSentCommits(); reveal_EveryCommitMsgIsRememberedByItsSender(); - assume TemporarilyDisableCheckpointing(c, v); + reveal_RecordedViewChangeMsgsAreValid(); + reveal_TemporarilyDisableCheckpointing(); } } diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index e34060e597..49b5eb9df4 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -64,14 +64,14 @@ module Replica { predicate IsActiveSeqID(lastStableCheckpoint:SequenceID, seqID:SequenceID) { - && lastStableCheckpoint <= seqID < lastStableCheckpoint + clusterConfig.workingWindowSize + && lastStableCheckpoint < seqID <= lastStableCheckpoint + clusterConfig.workingWindowSize } function getActiveSequenceIDs(lastStableCheckpoint:SequenceID) : set requires WF() { set seqID:SequenceID | && IsActiveSeqID(lastStableCheckpoint, seqID) // This statement provides a trigger by naming the constraint - && lastStableCheckpoint <= seqID < lastStableCheckpoint + clusterConfig.workingWindowSize // This statement satisfies Dafny's finite set heuristics. + && lastStableCheckpoint < seqID <= lastStableCheckpoint + clusterConfig.workingWindowSize // This statement satisfies Dafny's finite set heuristics. } } @@ -305,7 +305,7 @@ module Replica { // Node local invariants that we need to satisfy dafny requires. This gets proven as part of the Distributed system invariants. // That is why it can appear as enabling condition, but does not need to be translated to runtime checks to C++. // For this to be safe it has to appear in the main invarinat in the proof. - predicate LiteInv(c:Constants, v:Variables) { + predicate LiteInv(c:Constants, v:Variables) { //TODO: move to proof && v.WF(c) && (forall newViewMsg | newViewMsg in v.newViewMsgsRecvd.msgs :: && newViewMsg.payload.valid(c.clusterConfig.AgreementQuorum()) @@ -640,19 +640,14 @@ module Replica { && v' == v } - predicate ValidViewChangeMsg(msg:Network.Message, - networkMsgs:set>, - agreementQuorum:nat) + predicate ValidViewChangeMsg(c:Constants, + msg:Network.Message, + networkMsgs:set>) + requires c.WF() { && msg.payload.ViewChangeMsg? - // Check Checkpoint msg-s signatures: - && var checkpointMsgs := set c | c in msg.payload.proofForLastStable.msgs; - && checkpointMsgs <= networkMsgs - // Check Signatures for the Prepared Certificates: - && (forall seqID | seqID in msg.payload.certificates - :: && msg.payload.certificates[seqID].votes <= networkMsgs - && msg.payload.certificates[seqID].valid(agreementQuorum, seqID)) // TODO: refactor to put this in msg.payload.valid(agreementQuorum) - && msg.payload.valid(agreementQuorum) + // Check validity and signatures for the Prepared Certificates: + && msg.payload.checked(c.clusterConfig, networkMsgs) } predicate RecvViewChangeMsg(c:Constants, v:Variables, v':Variables, msgOps:Network.MessageOps) @@ -660,15 +655,7 @@ module Replica { && v.WF(c) && msgOps.IsRecv() && var msg := msgOps.recv.value; - && msg.payload.ViewChangeMsg? - // Check Checkpoint msg-s signatures: - && var checkpointMsgs := set c | c in msg.payload.proofForLastStable.msgs; - && checkpointMsgs <= msgOps.signedMsgsToCheck - // Check Signatures for the Prepared Certificates: - && (forall seqID | seqID in msg.payload.certificates - :: && msg.payload.certificates[seqID].votes <= msgOps.signedMsgsToCheck - && msg.payload.certificates[seqID].valid(c.clusterConfig.AgreementQuorum(), seqID)) - && msg.payload.valid(c.clusterConfig.AgreementQuorum()) + && ValidViewChangeMsg(c, msg, msgOps.signedMsgsToCheck) && v' == v.(viewChangeMsgsRecvd := v.viewChangeMsgsRecvd.(msgs := v.viewChangeMsgsRecvd.msgs + {msg})) } @@ -680,12 +667,11 @@ module Replica { && msg.payload.NewViewMsg? && CurrentPrimary(c, v) == msg.sender && msg.payload.newView == v.view - && msg.payload.vcMsgs.msgs <= msgOps.signedMsgsToCheck - // Check that all the PreparedCertificates are valid - && msg.payload.valid(c.clusterConfig.AgreementQuorum()) + // Check that all the PreparedCertificates are valid and the signatures are OK + && msg.payload.checked(c.clusterConfig, msgOps.signedMsgsToCheck) // We only allow the primary to select 1 set of View Change messages per view. && (forall storedMsg | storedMsg in v.newViewMsgsRecvd.msgs :: msg.payload.newView != storedMsg.payload.newView) - && v.workingWindow.lastStableCheckpoint == HighestStable(c, msg.payload.vcMsgs.msgs) + && v.workingWindow.lastStableCheckpoint == HighestStable(c, msg.payload.vcMsgs.msgs) //TODO: might be >= && v' == v.(newViewMsgsRecvd := v.newViewMsgsRecvd.(msgs := v.newViewMsgsRecvd.msgs + {msg})) } @@ -777,6 +763,7 @@ module Replica { && v.newViewMsgsRecvd.msgs == {} && v.countExecutedSeqIDs == 0 && v.checkpointMsgsRecvd.msgs == {} + && v.workingWindow.lastStableCheckpoint == 0 } // Jay Normal Form - Dafny syntactic sugar, useful for selecting the next step From c8a658abd8255c0469c37196972697494d78a6a7 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 30 Jan 2023 22:02:43 +0200 Subject: [PATCH 11/38] Code restructuring. * Move PrimaryForView to ClusterConfig. * Move functionality from checked that is not related to signature checks to valid predicate. * Remove LiteInv from replicas and move it into the global Inv but renamed to RecordedNewViewMsgsAreValid. --- docs/sbft-formal-model/cluster_config.s.dfy | 8 ++++ docs/sbft-formal-model/messages.dfy | 45 ++++++++++++--------- docs/sbft-formal-model/proof.dfy | 30 ++++++++------ docs/sbft-formal-model/replica.i.dfy | 30 ++++---------- 4 files changed, 57 insertions(+), 56 deletions(-) diff --git a/docs/sbft-formal-model/cluster_config.s.dfy b/docs/sbft-formal-model/cluster_config.s.dfy index 0ea42e862e..41fa93540d 100644 --- a/docs/sbft-formal-model/cluster_config.s.dfy +++ b/docs/sbft-formal-model/cluster_config.s.dfy @@ -14,6 +14,8 @@ include "network.s.dfy" module ClusterConfig { import opened HostIdentifiers + type ViewNum = nat + datatype Constants = Constants( maxByzantineFaultyReplicas:nat, numClients:nat, @@ -32,6 +34,12 @@ module ClusterConfig { N() + numClients } + function PrimaryForView(view:ViewNum) : nat + requires WF() + { + view % N() + } + function F() : nat requires WF() { diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index 627cf85cb3..b537c0ec78 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -17,10 +17,9 @@ module Messages { import Library import opened HostIdentifiers import Network - import ClusterConfig + import opened ClusterConfig type SequenceID = nat - type ViewNum = nat datatype ClientOperation = ClientOperation(sender:HostId, timestamp:nat) @@ -56,13 +55,15 @@ module Messages { } datatype ViewChangeMsgsSelectedByPrimary = ViewChangeMsgsSelectedByPrimary(msgs:set>) { - predicate valid(view:ViewNum, quorumSize:nat) { + predicate valid(view:ViewNum, clusterConfig:ClusterConfig.Constants) + requires clusterConfig.WF() + { && |msgs| > 0 && (forall v | v in msgs :: && v.payload.ViewChangeMsg? - && v.payload.validViewChangeMsg(quorumSize) + && v.payload.validViewChangeMsg(clusterConfig) && v.payload.newView == view) // All the ViewChange messages have to be for the same View. && UniqueSenders(msgs) - && |msgs| == quorumSize //TODO: once proof is complete try with >= + && |msgs| == clusterConfig.AgreementQuorum() //TODO: once proof is complete try with >= } } @@ -104,10 +105,11 @@ module Messages { | NewViewMsg(newView:ViewNum, vcMsgs:ViewChangeMsgsSelectedByPrimary) | CheckpointMsg(seqIDReached:SequenceID, committedClientOperations:CommittedClientOperations) { - predicate valid(quorumSize:nat) + predicate valid(clusterConfig:ClusterConfig.Constants) + requires clusterConfig.WF() { - && (ViewChangeMsg? ==> validViewChangeMsg(quorumSize)) - && (NewViewMsg? ==> validNewViewMsg(quorumSize)) + && (ViewChangeMsg? ==> validViewChangeMsg(clusterConfig)) + && (NewViewMsg? ==> validNewViewMsg(clusterConfig)) } predicate checked(clusterConfig:ClusterConfig.Constants, sigChecks:set>) requires clusterConfig.WF() @@ -115,36 +117,39 @@ module Messages { && (ViewChangeMsg? ==> checkedViewChangeMsg(clusterConfig, sigChecks)) && (NewViewMsg? ==> checkedNewViewMsg(clusterConfig, sigChecks)) } - predicate validViewChangeMsg(quorumSize:nat) + predicate validViewChangeMsg(clusterConfig:ClusterConfig.Constants) + requires clusterConfig.WF() requires ViewChangeMsg? { - proofForLastStable.valid(lastStableCheckpoint, quorumSize) + && (forall seqID | seqID in certificates + :: && (forall replica | replica in sendersOf(certificates[seqID].votes) + :: clusterConfig.IsReplica(replica)) + && certificates[seqID].valid(clusterConfig.AgreementQuorum(), seqID)) + && proofForLastStable.valid(lastStableCheckpoint, clusterConfig.AgreementQuorum()) } - predicate validNewViewMsg(quorumSize:nat) + predicate validNewViewMsg(clusterConfig:ClusterConfig.Constants) + requires clusterConfig.WF() requires NewViewMsg? { - vcMsgs.valid(newView, quorumSize) + && vcMsgs.valid(newView, clusterConfig) + && (forall replica | replica in sendersOf(vcMsgs.msgs) + :: clusterConfig.IsReplica(replica)) } predicate checkedViewChangeMsg(clusterConfig:ClusterConfig.Constants, sigChecks:set>) requires ViewChangeMsg? requires clusterConfig.WF() { - && valid(clusterConfig.AgreementQuorum()) + && valid(clusterConfig) // Check Checkpoint msg-s signatures: && proofForLastStable.msgs <= sigChecks && (forall seqID | seqID in certificates - :: && certificates[seqID].votes <= sigChecks - && (forall replica | replica in sendersOf(certificates[seqID].votes) - :: clusterConfig.IsReplica(replica)) - && certificates[seqID].valid(clusterConfig.AgreementQuorum(), seqID)) + :: && certificates[seqID].votes <= sigChecks) } predicate checkedNewViewMsg(clusterConfig:ClusterConfig.Constants, sigChecks:set>) requires NewViewMsg? requires clusterConfig.WF() { - && valid(clusterConfig.AgreementQuorum()) - && (forall replica | replica in sendersOf(vcMsgs.msgs) - :: clusterConfig.IsReplica(replica)) + && valid(clusterConfig) && vcMsgs.msgs <= sigChecks } predicate IsIntraViewMsg() { diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index b614d03b5c..0b53194c41 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -304,17 +304,21 @@ module Proof { // :: prePrepare1 == prePrepare2) // } - predicate {:opaque} AllReplicasLiteInv (c: Constants, v:Variables) { + predicate {:opaque} RecordedNewViewMsgsAreValid(c:Constants, v:Variables) { && v.WF(c) - && (forall replicaIdx | 0 <= replicaIdx < |c.hosts| && c.clusterConfig.IsHonestReplica(replicaIdx) - :: Replica.LiteInv(c.hosts[replicaIdx].replicaConstants, v.hosts[replicaIdx].replicaVariables)) + && (forall replicaIdx, newViewMsg | + && c.clusterConfig.IsHonestReplica(replicaIdx) + && var replicaVariables := v.hosts[replicaIdx].replicaVariables; + && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs + :: && newViewMsg.payload.valid(c.clusterConfig) + && c.clusterConfig.PrimaryForView(newViewMsg.payload.newView) == newViewMsg.sender) } predicate Inv(c: Constants, v:Variables) { //&& PrePreparesCarrySameClientOpsForGivenSeqID(c, v) // Do not remove, lite invariant about internal honest Node invariants: && v.WF(c) - && AllReplicasLiteInv(c, v) + && RecordedNewViewMsgsAreValid(c, v) && RecordedPreparesHaveValidSenderID(c, v) //&& SentPreparesMatchRecordedPrePrepareIfHostInSameView(c, v) && RecordedPrePreparesRecvdCameFromNetwork(c, v) @@ -505,12 +509,12 @@ module Proof { && Replica.NextStep(h_c, h_v, h_v', step.msgOps, h_step) } - lemma HonestPreservesAllReplicasLiteInv(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + lemma HonestPreservesRecordedNewViewMsgsAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) - ensures AllReplicasLiteInv(c, v') + ensures RecordedNewViewMsgsAreValid(c, v') { - reveal_AllReplicasLiteInv(); + reveal_RecordedNewViewMsgsAreValid(); } lemma HonestPreservesRecordedPreparesHaveValidSenderID(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -877,7 +881,7 @@ module Proof { { /// // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - // reveal_AllReplicasLiteInv(); + // reveal_RecordedNewViewMsgsAreValid(); // reveal_RecordedPreparesHaveValidSenderID(); reveal_RecordedPrePreparesRecvdCameFromNetwork(); // reveal_RecordedPreparesRecvdCameFromNetwork(); @@ -1509,7 +1513,7 @@ module Proof { { reveal_EveryCommitMsgIsRememberedByItsSender(); - // reveal_AllReplicasLiteInv(); + // reveal_RecordedNewViewMsgsAreValid(); // reveal_RecordedPreparesHaveValidSenderID(); // reveal_RecordedPrePreparesRecvdCameFromNetwork(); // reveal_RecordedPreparesRecvdCameFromNetwork(); @@ -1573,7 +1577,7 @@ module Proof { var h_v' := v'.hosts[step.id].replicaVariables; var h_step :| Replica.NextStep(h_c, h_v, h_v', step.msgOps, h_step); assert HonestReplicaStepTaken(c, v, v', step, h_v, h_step); - HonestPreservesAllReplicasLiteInv(c, v, v', step, h_v, h_step); + HonestPreservesRecordedNewViewMsgsAreValid(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesHaveValidSenderID(c, v, v', step, h_v, h_step); HonestPreservesRecordedPrePreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); @@ -1611,8 +1615,8 @@ module Proof { requires (c.clusterConfig.IsFaultyReplica(step.id) || c.clusterConfig.IsClient(step.id)) ensures Inv(c, v') { - assert AllReplicasLiteInv(c, v') by { - reveal_AllReplicasLiteInv(); + assert RecordedNewViewMsgsAreValid(c, v') by { + reveal_RecordedNewViewMsgsAreValid(); } assert RecordedPreparesHaveValidSenderID(c, v') by { reveal_RecordedPreparesHaveValidSenderID(); @@ -1697,7 +1701,7 @@ module Proof { ensures Inv(c, v) { assert Inv(c, v) by { - reveal_AllReplicasLiteInv(); + reveal_RecordedNewViewMsgsAreValid(); reveal_RecordedCommitsClientOpsMatchPrePrepare(); reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 49b5eb9df4..fc1f3b6ad4 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -18,7 +18,7 @@ module Replica { import opened HostIdentifiers import opened Messages import Network - import ClusterConfig + import opened ClusterConfig type PrepareProofSet = map> predicate PrepareProofSetWF(c:Constants, ps:PrepareProofSet) @@ -163,16 +163,10 @@ module Replica { } } - function PrimaryForView(c:Constants, view:ViewNum) : nat - requires c.WF() - { - view % c.clusterConfig.N() - } - function CurrentPrimary(c:Constants, v:Variables) : nat requires v.WF(c) { - PrimaryForView(c, v.view) + c.clusterConfig.PrimaryForView(v.view) } predicate HaveSufficientVCMsgsToMoveTo(c:Constants, v:Variables, newView:ViewNum) @@ -302,24 +296,13 @@ module Replica { v.workingWindow.prePreparesRcvd[seqID := Some(msgOps.send.value)])) } - // Node local invariants that we need to satisfy dafny requires. This gets proven as part of the Distributed system invariants. - // That is why it can appear as enabling condition, but does not need to be translated to runtime checks to C++. - // For this to be safe it has to appear in the main invarinat in the proof. - predicate LiteInv(c:Constants, v:Variables) { //TODO: move to proof - && v.WF(c) - && (forall newViewMsg | newViewMsg in v.newViewMsgsRecvd.msgs :: - && newViewMsg.payload.valid(c.clusterConfig.AgreementQuorum()) - && PrimaryForView(c, newViewMsg.payload.newView) == newViewMsg.sender) - } - predicate CurrentNewViewMsg(c:Constants, v:Variables, newViewMsg:Network.Message) { && v.WF(c) - && LiteInv(c, v) && newViewMsg.payload.NewViewMsg? - && newViewMsg.payload.valid(c.clusterConfig.AgreementQuorum()) + && newViewMsg.payload.valid(c.clusterConfig) && newViewMsg in v.newViewMsgsRecvd.msgs && newViewMsg.payload.newView == v.view && CurrentPrimary(c, v) == newViewMsg.sender @@ -344,7 +327,6 @@ module Replica { predicate IsValidPrePrepare(c:Constants, v:Variables, msg:Network.Message) { && v.WF(c) - && LiteInv(c, v) && msg.payload.PrePrepare? && msg.payload.seqID in v.workingWindow.getActiveSequenceIDs(c) && c.clusterConfig.IsReplica(msg.sender) @@ -358,6 +340,8 @@ module Replica { then true else && |newViewMsgs| == 1 && var newViewMsg :| newViewMsg in newViewMsgs; + && newViewMsg.payload.valid(c.clusterConfig) + && c.clusterConfig.PrimaryForView(newViewMsg.payload.newView) == newViewMsg.sender && IsValidOperationWrapper(c, v, msg.payload.seqID, newViewMsg, msg.payload.operationWrapper)) } @@ -532,7 +516,7 @@ module Replica { v.workingWindow.lastStableCheckpoint, CheckpointsQuorum(ExtractStableCheckpointProof(c, v)), ExtractCertificatesFromWorkingWindow(c, v))); - // TODO: this should follow from the invariant and from the way we collect prepares. Might be put in LiteInv. + // TODO: this should follow from the invariant and from the way we collect prepares. // && (forall seqID :: seqID in vcMsg.payload.certificates ==> // (vcMsg.payload.certificates[seqID].valid(c.clusterConfig.AgreementQuorum()))) && v' == v.(view := newView) @@ -621,7 +605,7 @@ module Replica { :: msg.payload.newView != v.view) // We can only select 1 set of VC msgs && (forall vcMsg | vcMsg in viewChangeMsgsSelectedByPrimary.msgs :: && vcMsg in v.viewChangeMsgsRecvd.msgs) - && viewChangeMsgsSelectedByPrimary.valid(v.view, c.clusterConfig.AgreementQuorum()) + && viewChangeMsgsSelectedByPrimary.valid(v.view, c.clusterConfig) && var newViewMsg := Network.Message(c.myId, NewViewMsg(v.view, viewChangeMsgsSelectedByPrimary)); && v' == v.(newViewMsgsRecvd := v.newViewMsgsRecvd.(msgs := v.newViewMsgsRecvd.msgs + {newViewMsg})) From 0067a3b2a480e775a7c2b78530236df5ae12d1b8 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Fri, 3 Feb 2023 18:14:25 +0200 Subject: [PATCH 12/38] Lemma HonestPreservesRecordedViewChangeMsgsAreValid verified. * Completed the verification of lemma HonestPreservesRecordedViewChangeMsgsAreValid. * Added triggering changes in replica.dfy in regard to the verification. * Refactoring in PreparedCertificate to collect all conditions for a valid check in 2 predicates - validFull() and empty(). --- docs/sbft-formal-model/messages.dfy | 24 ++++++++------ docs/sbft-formal-model/proof.dfy | 47 +++++++++++++++++----------- docs/sbft-formal-model/replica.i.dfy | 3 +- 3 files changed, 45 insertions(+), 29 deletions(-) diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index b537c0ec78..3699402a97 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -41,13 +41,21 @@ module Messages { predicate WF() { (forall v | v in votes :: v.payload.Prepare?) } - predicate valid(quorumSize:nat, seqID:SequenceID) { + predicate validFull(clusterConfig:ClusterConfig.Constants, seqID:SequenceID) + requires clusterConfig.WF() + { + && |votes| >= clusterConfig.AgreementQuorum() + && WF() + && prototype().seqID == seqID + && (forall v | v in votes :: v.payload == prototype()) // messages have to be votes that match eachother by the prototype + && UniqueSenders(votes) + && (forall v | v in votes :: clusterConfig.IsReplica(v.sender)) + } + predicate valid(clusterConfig:ClusterConfig.Constants, seqID:SequenceID) + requires clusterConfig.WF() + { || empty() - || (&& |votes| == quorumSize - && WF() - && prototype().seqID == seqID - && (forall v | v in votes :: v.payload == prototype()) // messages have to be votes that match eachother by the prototype - && UniqueSenders(votes)) + || validFull(clusterConfig, seqID) } predicate empty() { && |votes| == 0 @@ -122,9 +130,7 @@ module Messages { requires ViewChangeMsg? { && (forall seqID | seqID in certificates - :: && (forall replica | replica in sendersOf(certificates[seqID].votes) - :: clusterConfig.IsReplica(replica)) - && certificates[seqID].valid(clusterConfig.AgreementQuorum(), seqID)) + :: certificates[seqID].valid(clusterConfig, seqID)) && proofForLastStable.valid(lastStableCheckpoint, clusterConfig.AgreementQuorum()) } predicate validNewViewMsg(clusterConfig:ClusterConfig.Constants) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 0b53194c41..0d578d50db 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -94,7 +94,7 @@ module Proof { :: && var h_c := c.hosts[commitMsg.sender].replicaConstants; && var h_v := v.hosts[commitMsg.sender].replicaVariables; && var certificate := Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID); - && certificate.valid(c.clusterConfig.AgreementQuorum(), commitMsg.payload.seqID) + && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) && !certificate.empty() && certificate.prototype().view >= commitMsg.payload.view // && (certificate.prototype().view == commitMsg.payload.view @@ -162,7 +162,8 @@ module Proof { && var prepareMap := v.hosts[replicaIdx].replicaVariables.workingWindow.preparesRcvd; && seqID in prepareMap && sender in prepareMap[seqID] - :: && v.hosts[replicaIdx].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender + :: && c.clusterConfig.IsReplica(sender) + && v.hosts[replicaIdx].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender ) } @@ -1144,7 +1145,7 @@ module Proof { && IsHonestReplica(c, viewChangeMsg.sender) :: && commitMsg.payload.seqID in viewChangeMsg.payload.certificates && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; - && certificate.valid(c.clusterConfig.AgreementQuorum(), commitMsg.payload.seqID) + && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) && !certificate.empty() && certificate.prototype().view >= commitMsg.payload.view // && certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper @@ -1416,6 +1417,7 @@ module Proof { ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') { reveal_UnCommitableAgreesWithRecordedPrePrepare(); + reveal_RecordedNewViewMsgsAreValid(); forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | && IsHonestReplica(c, replicaIdx) && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; @@ -1512,7 +1514,7 @@ module Proof { ensures EveryCommitMsgIsRememberedByItsSender(c, v') { reveal_EveryCommitMsgIsRememberedByItsSender(); - + assume false; // reveal_RecordedNewViewMsgsAreValid(); // reveal_RecordedPreparesHaveValidSenderID(); // reveal_RecordedPrePreparesRecvdCameFromNetwork(); @@ -1531,13 +1533,13 @@ module Proof { // reveal_CommitMsgsFromHonestSendersAgree(); // reveal_RecordedCheckpointsRecvdCameFromNetwork(); // reveal_UnCommitableAgreesWithPrepare(); - // reveal_UnCommitableAgreesWithRecordedPrePrepare();//Unfinished proof. + // reveal_UnCommitableAgreesWithRecordedPrePrepare(); // reveal_HonestReplicasLeaveViewsBehind(); // reveal_RecordedNewViewMsgsContainSentVCMsgs(); // reveal_RecordedViewChangeMsgsCameFromNetwork(); - // reveal_SentViewChangesMsgsComportWithSentCommits();//Unfinished proof. - // reveal_EveryCommitMsgIsRememberedByItsSender();//Unfinished proof. - // reveal_RecordedViewChangeMsgsAreValid();//Unfinished proof. + // reveal_SentViewChangesMsgsComportWithSentCommits(); + // reveal_EveryCommitMsgIsRememberedByItsSender(); + // reveal_RecordedViewChangeMsgsAreValid(); // reveal_TemporarilyDisableCheckpointing(); } @@ -1549,22 +1551,29 @@ module Proof { reveal_RecordedViewChangeMsgsAreValid(); reveal_TemporarilyDisableCheckpointing(); // Needed for LeaveViewStep if (h_step.LeaveViewStep?) { - reveal_RecordedPreparesHaveValidSenderID(); - reveal_RecordedPrePreparesRecvdCameFromNetwork(); reveal_RecordedPreparesRecvdCameFromNetwork(); - reveal_RecordedPrePreparesMatchHostView(); reveal_RecordedPreparesMatchHostView(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); - reveal_RecordedCommitsClientOpsMatchPrePrepare(); - reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); - reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); - reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - reveal_HonestReplicasLockOnPrepareForGivenView(); - assume false; + reveal_TemporarilyDisableCheckpointing(); + + forall replicaIdx, viewChangeMsg | + && IsHonestReplica(c, replicaIdx) + && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; + && viewChangeMsg in replicaVariables'.viewChangeMsgsRecvd.msgs + ensures (&& var replicaConstants := c.hosts[replicaIdx].replicaConstants; + && Replica.ValidViewChangeMsg(replicaConstants, + viewChangeMsg, + v'.network.sentMsgs)) + { + if(viewChangeMsg !in v.hosts[replicaIdx].replicaVariables.viewChangeMsgsRecvd.msgs) { + Messages.reveal_UniqueSenders(); + assert viewChangeMsg.payload.lastStableCheckpoint == 0; + assume |viewChangeMsg.payload.proofForLastStable.msgs| == 0; // TODO: remove once we introduce Checkpointing reasoning + } + } } } + lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index fc1f3b6ad4..d7c992c9dd 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -571,7 +571,8 @@ module Replica { requires v.WF(c) requires seqID in v.workingWindow.getActiveSequenceIDs(c) { - var workingWindowPreparesRecvd := v.workingWindow.preparesRcvd[seqID].Values; + var workingWindowPreparesRecvd := set key | key in v.workingWindow.preparesRcvd[seqID] // This set comprehension is the same as calling .Values, + :: v.workingWindow.preparesRcvd[seqID][key];// but this way it is better for triggering in Dafny var viewChangeMsgPreparesRecvd := ExtractPreparedCertificateFromLatestViewChangeMsg(c, v, seqID).votes; if |workingWindowPreparesRecvd| >= c.clusterConfig.AgreementQuorum() then PreparedCertificate(workingWindowPreparesRecvd) From 1c8c602dc0adad2b3946036ba91c9b0a83df0fc1 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 6 Feb 2023 12:41:06 +0200 Subject: [PATCH 13/38] Addition of new Lemma and Inv predicate. Preparation for new lemma DoubleAgentDidNotCommit and Inv predicate CommitCertificateEstablishesUncommitableInView. Minor code cleanup. --- docs/sbft-formal-model/proof.dfy | 50 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 0d578d50db..396870974e 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -344,8 +344,9 @@ module Proof { && RecordedViewChangeMsgsCameFromNetwork(c, v) && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. && EveryCommitMsgIsRememberedByItsSender(c, v)//Unfinished proof. - && RecordedViewChangeMsgsAreValid(c, v)//Unfinished proof. + && RecordedViewChangeMsgsAreValid(c, v) && TemporarilyDisableCheckpointing(c, v) + // && CommitCertificateEstablishesUncommitableInView(c, v) // "AllPreparedCertsInWorkingWindowAreValid" } @@ -904,14 +905,6 @@ module Proof { reveal_RecordedPrePreparesMatchHostView(); /// - // if (h_step.SendPrepareStep?) { - // assume false; - // } else if (h_step.LeaveViewStep?) { - // assume false; - // } else if (h_step.SendCommitStep?) { - // assume false; - // } - reveal_UnCommitableAgreesWithPrepare(); //TODO: minimise proof forall prepareMsg:Message, @@ -997,12 +990,12 @@ module Proof { } } - predicate ReplicasThatSentCommit(c:Constants, - v:Variables, - seqID:Messages.SequenceID, - view:nat, - operationWrapper:Messages.OperationWrapper, - replica:HostIdentifiers.HostId) + predicate ReplicaSentCommit(c:Constants, + v:Variables, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper, + replica:HostIdentifiers.HostId) requires v.WF(c) { && c.clusterConfig.IsHonestReplica(replica) @@ -1033,7 +1026,7 @@ module Proof { requires v.WF(c) { set replica | replica in getAllReplicas(c) && - (|| ReplicasThatSentCommit(c, v, seqID, view, operationWrapper, replica) + (|| ReplicaSentCommit(c, v, seqID, view, operationWrapper, replica) || ReplicasInViewOrLower(c, v, seqID, view, operationWrapper, replica) || c.clusterConfig.IsFaultyReplica(replica)) } @@ -1410,6 +1403,21 @@ module Proof { reveal_RecordedViewChangeMsgsCameFromNetwork(); } + // This is part of a contradiction proof + lemma DoubleAgentDidNotCommit(c: Constants, + v:Variables, + doubleAgent:HostId, + seqID:Messages.SequenceID, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper, + vcMsg:Message) + requires Inv(c, v) + requires c.clusterConfig.IsHonestReplica(doubleAgent) + ensures !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) + { + + } + lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1436,7 +1444,7 @@ module Proof { && msg.payload.newView == h_v.view; // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare if |newViewMsgs| == 0 { assert h_v.view == 0; - assert false; + assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. } var newViewMsg :| newViewMsg in newViewMsgs; var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); @@ -1446,7 +1454,6 @@ module Proof { UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); assert viewChangers <= getAllReplicas(c) by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); - //assume false; // Need an invariant on the VC messages. } var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); assert !c.clusterConfig.IsFaultyReplica(doubleAgent); @@ -1456,12 +1463,11 @@ module Proof { assert vcMsg.payload.ViewChangeMsg?; assert vcMsg in v.network.sentMsgs by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); - //assume false; } - assert !ReplicasThatSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // Need to finish this proof reveal_SentViewChangesMsgsComportWithSentCommits(); - assume false; + // assume false; } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { reveal_HonestReplicasLeaveViewsBehind(); @@ -1573,7 +1579,7 @@ module Proof { } } } - + lemma InvariantNext(c: Constants, v:Variables, v':Variables) requires Inv(c, v) requires Next(c, v, v') From d3f79caccfbeb536a626eaf8701ff101b000b752 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 27 Feb 2023 10:41:14 +0200 Subject: [PATCH 14/38] Need to introduce Commit Certificate. --- docs/sbft-formal-model/proof.dfy | 107 +++++++++++++++++++++++++------ 1 file changed, 87 insertions(+), 20 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 396870974e..80d6fb4037 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -97,11 +97,22 @@ module Proof { && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) && !certificate.empty() && certificate.prototype().view >= commitMsg.payload.view - // && (certificate.prototype().view == commitMsg.payload.view - // ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + && (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) ) } + predicate {:opaque} ViewChangeMsgsFromHonestInNetworkAreValid(c:Constants, v:Variables) { + && v.WF(c) + && (forall viewChangeMsg | + && viewChangeMsg in v.network.sentMsgs + && viewChangeMsg.payload.ViewChangeMsg? + && var replicaIdx := viewChangeMsg.sender; + && IsHonestReplica(c, replicaIdx) + :: && var replicaIdx := viewChangeMsg.sender; + && viewChangeMsg.payload.checked(c.clusterConfig, v.network.sentMsgs)) + } + predicate {:opaque} TemporarilyDisableCheckpointing(c:Constants, v:Variables) { && v.WF(c) && (forall replicaID | && IsHonestReplica(c, replicaID) @@ -342,10 +353,12 @@ module Proof { && HonestReplicasLeaveViewsBehind(c, v) && RecordedNewViewMsgsContainSentVCMsgs(c, v) && RecordedViewChangeMsgsCameFromNetwork(c, v) - && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. + && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. This predicate is false. && EveryCommitMsgIsRememberedByItsSender(c, v)//Unfinished proof. && RecordedViewChangeMsgsAreValid(c, v) + && ViewChangeMsgsFromHonestInNetworkAreValid(c, v)//Unfinished proof. && TemporarilyDisableCheckpointing(c, v) + // && NewViewMsgsReflectPriorCommitCertificates(c, v) // && CommitCertificateEstablishesUncommitableInView(c, v) // "AllPreparedCertsInWorkingWindowAreValid" } @@ -1125,24 +1138,43 @@ module Proof { v.network.sentMsgs)) } + // This predicate is false and will be refactored to SentViewChangesMsgsComportWithUncommitableInView predicate {:opaque} SentViewChangesMsgsComportWithSentCommits(c:Constants, v:Variables) { + && true + // && v.WF(c) + // && (forall viewChangeMsg, commitMsg | + // && viewChangeMsg in v.network.sentMsgs + // && commitMsg in v.network.sentMsgs + // && viewChangeMsg.payload.ViewChangeMsg? + // && commitMsg.payload.Commit? + // && commitMsg.payload.view <= viewChangeMsg.payload.newView + // && commitMsg.sender == viewChangeMsg.sender + // //TODO: add Shift consequences (this works only if no one advances the WW) + // && IsHonestReplica(c, viewChangeMsg.sender) + // :: && commitMsg.payload.seqID in viewChangeMsg.payload.certificates + // && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; + // && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) + // && !certificate.empty() + // && certificate.prototype().view >= commitMsg.payload.view + // // && (commitMsg.payload.view == viewChangeMsg.payload.newView) ==> + // // certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper + // ) + } + + predicate {:opaque} SentViewChangesMsgsComportWithUncommitableInView(c:Constants, v:Variables) { && v.WF(c) - && (forall viewChangeMsg, commitMsg | - && viewChangeMsg in v.network.sentMsgs - && commitMsg in v.network.sentMsgs - && viewChangeMsg.payload.ViewChangeMsg? - && commitMsg.payload.Commit? - && commitMsg.payload.view <= viewChangeMsg.payload.newView - && commitMsg.sender == viewChangeMsg.sender - //TODO: add Shift consequences (this works only if no one advances the WW) - && IsHonestReplica(c, viewChangeMsg.sender) - :: && commitMsg.payload.seqID in viewChangeMsg.payload.certificates - && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; - && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) - && !certificate.empty() - && certificate.prototype().view >= commitMsg.payload.view - // && certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper - ) + && (forall viewChangeMsg, + seqID:Messages.SequenceID, + view:nat, + operationWrapper:Messages.OperationWrapper | + && viewChangeMsg in v.network.sentMsgs + && viewChangeMsg.payload.ViewChangeMsg? + && IsHonestReplica(c, viewChangeMsg.sender) + && view < viewChangeMsg.payload.view + && var certificate := viewChangeMsg.payload.certificates[seqID]; + && !certificate.empty() + && operationWrapper != certificate.prototype().operationWrapper + :: && UnCommitableInView(c, v, seqID, view, operationWrapper)) } //TODO: write a predicate Prepare matches Commit @@ -1466,7 +1498,11 @@ module Proof { } assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // Need to finish this proof - reveal_SentViewChangesMsgsComportWithSentCommits(); + // viewchangemessages from honest sender comport with uncommitable in view + reveal_SentViewChangesMsgsComportWithUncommitableInView(); + assume SentViewChangesMsgsComportWithUncommitableInView(c, v); + assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); + // Left off here. // assume false; } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { @@ -1511,6 +1547,32 @@ module Proof { ensures SentViewChangesMsgsComportWithSentCommits(c, v') { reveal_SentViewChangesMsgsComportWithSentCommits(); + + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_RecordedPreparesHaveValidSenderID(); + reveal_RecordedPrePreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesMatchHostView(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); + reveal_RecordedCommitsClientOpsMatchPrePrepare(); + reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); + reveal_HonestReplicasLockOnPrepareForGivenView(); + reveal_HonestReplicasLockOnCommitForGivenView(); + reveal_CommitMsgsFromHonestSendersAgree(); + reveal_RecordedCheckpointsRecvdCameFromNetwork(); + reveal_HonestReplicasLockOnCommitForGivenView(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + h_v.workingWindow.reveal_Shift(); + } + + lemma HonestPreservesViewChangeMsgsFromHonestInNetworkAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures ViewChangeMsgsFromHonestInNetworkAreValid(c, v') + { + reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); assume false; } @@ -1616,6 +1678,7 @@ module Proof { HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesSentViewChangesMsgsComportWithSentCommits(c, v, v', step, h_v, h_step); HonestPreservesEveryCommitMsgIsRememberedByItsSender(c, v, v', step, h_v, h_step); + HonestPreservesViewChangeMsgsFromHonestInNetworkAreValid(c, v, v', step, h_v, h_step); HonestPreservesRecordedViewChangeMsgsAreValid(c, v, v', step, h_v, h_step); assume TemporarilyDisableCheckpointing(c, v'); } else { @@ -1708,6 +1771,9 @@ module Proof { assert RecordedViewChangeMsgsAreValid(c, v') by { reveal_RecordedViewChangeMsgsAreValid(); } + assert ViewChangeMsgsFromHonestInNetworkAreValid(c, v') by { + reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); + } assume TemporarilyDisableCheckpointing(c, v'); } @@ -1741,6 +1807,7 @@ module Proof { reveal_SentViewChangesMsgsComportWithSentCommits(); reveal_EveryCommitMsgIsRememberedByItsSender(); reveal_RecordedViewChangeMsgsAreValid(); + reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); reveal_TemporarilyDisableCheckpointing(); } } From 783a3cf71e2c14d546bdb9e218938762535d73bb Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 1 Mar 2023 10:51:23 +0200 Subject: [PATCH 15/38] Initial steps for proving HonestPreservesEveryCommitMsgIsRememberedByItsSender. --- docs/sbft-formal-model/proof.dfy | 76 +++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 80d6fb4037..1540bc3823 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1582,7 +1582,81 @@ module Proof { ensures EveryCommitMsgIsRememberedByItsSender(c, v') { reveal_EveryCommitMsgIsRememberedByItsSender(); - assume false; + + forall commitMsg | && commitMsg in v'.network.sentMsgs + && commitMsg.payload.Commit? + && IsHonestReplica(c, commitMsg.sender) + && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v' := v'.hosts[commitMsg.sender].replicaVariables; + && commitMsg.payload.seqID in h_v'.workingWindow.getActiveSequenceIDs(h_c) + ensures && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v' := v'.hosts[commitMsg.sender].replicaVariables; + && var certificate := Replica.ExtractCertificateForSeqID(h_c, h_v', commitMsg.payload.seqID); + && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) + && !certificate.empty() + && certificate.prototype().view >= commitMsg.payload.view + && (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + { + + /* + | SendPrePrepareStep(seqID:SequenceID) + | RecvPrePrepareStep() + | SendPrepareStep(seqID:SequenceID) + | RecvPrepareStep() + | SendCommitStep(seqID:SequenceID) + | RecvCommitStep() + | DoCommitStep(seqID:SequenceID) + | ExecuteStep(seqID:SequenceID) + | SendCheckpointStep(seqID:SequenceID) + | RecvCheckpointStep() + | AdvanceWorkingWindowStep(seqID:SequenceID, checkpointsQuorum:CheckpointsQuorum) + | PerformStateTransferStep(seqID:SequenceID, checkpointsQuorum:CheckpointsQuorum) + //| SendReplyToClient(seqID:SequenceID) + // TODO: uncomment those steps when we start working on the proof + | LeaveViewStep(newView:ViewNum) + | SendViewChangeMsgStep() + | RecvViewChangeMsgStep() + | SelectQuorumOfViewChangeMsgsStep(viewChangeMsgsSelectedByPrimary:ViewChangeMsgsSelectedByPrimary) + | SendNewViewMsgStep() + | RecvNewViewMsgStep() + */ + var h_c := c.hosts[step.id].replicaConstants; + var h_v' := v'.hosts[step.id].replicaVariables; + reveal_TemporarilyDisableCheckpointing(); + if(h_step.LeaveViewStep?) { + assume false; + } else if(h_step.SendCommitStep?) { + assume false; + } else if(h_step.RecvPrepareStep?) { + assume false; + } else if(h_step.PerformStateTransferStep?) { + assume false; + } else if(h_step.SendViewChangeMsgStep?) { + assume false; + } else if(h_step.RecvPrePrepareStep?) { + assume false; + } else if(h_step.AdvanceWorkingWindowStep?) { + assume false; + } else if(h_step.RecvCommitStep?) { + assert h_v.workingWindow.getActiveSequenceIDs(h_c) == h_v'.workingWindow.getActiveSequenceIDs(h_c); + assert Replica.RecvCommit(h_c, h_v, h_v', step.msgOps); + assert h_v'.workingWindow == + h_v.workingWindow.(commitsRcvd := h_v'.workingWindow.commitsRcvd); + assert h_v.workingWindow.preparesRcvd == h_v'.workingWindow.preparesRcvd; + var workingWindowPreparesRecvd := set key | key in h_v.workingWindow.preparesRcvd[commitMsg.payload.seqID] + :: h_v.workingWindow.preparesRcvd[commitMsg.payload.seqID][key]; + var workingWindowPreparesRecvd' := set key | key in h_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID] + :: h_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID][key]; + assert workingWindowPreparesRecvd == workingWindowPreparesRecvd'; + assert Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID) == + Replica.ExtractCertificateForSeqID(h_c, h_v', commitMsg.payload.seqID); + } else { + assume false; + } + } + + //assume false; // reveal_RecordedNewViewMsgsAreValid(); // reveal_RecordedPreparesHaveValidSenderID(); // reveal_RecordedPrePreparesRecvdCameFromNetwork(); From 1183fdfea0b06139d52990578a2d1a2cd2dee778 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 2 Mar 2023 11:39:02 +0200 Subject: [PATCH 16/38] HonestPreservesEveryCommitMsgIsRememberedByItsSender proven for trivial steps. --- docs/sbft-formal-model/proof.dfy | 82 ++++++++++++++++++++++++-------- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 1540bc3823..50924290ae 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -353,6 +353,7 @@ module Proof { && HonestReplicasLeaveViewsBehind(c, v) && RecordedNewViewMsgsContainSentVCMsgs(c, v) && RecordedViewChangeMsgsCameFromNetwork(c, v) + && OneViewChangeMessageFromReplicaPerView(c, v) && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. This predicate is false. && EveryCommitMsgIsRememberedByItsSender(c, v)//Unfinished proof. && RecordedViewChangeMsgsAreValid(c, v) @@ -1126,6 +1127,19 @@ module Proof { :: viewChangeMsg in v.network.sentMsgs) } + predicate {:opaque} OneViewChangeMessageFromReplicaPerView(c:Constants, v:Variables) { + && v.WF(c) + && (forall msg1, msg2 | + && msg1 in v.network.sentMsgs + && msg2 in v.network.sentMsgs + && msg1.sender == msg2.sender + && IsHonestReplica(c, msg1.sender) + && msg1.payload.ViewChangeMsg? + && msg2.payload.ViewChangeMsg? + && msg1.payload.newView == msg2.payload.newView + :: msg1 == msg2) + } + predicate {:opaque} RecordedViewChangeMsgsAreValid(c:Constants, v:Variables) { && v.WF(c) && (forall replicaIdx, viewChangeMsg | @@ -1435,6 +1449,17 @@ module Proof { reveal_RecordedViewChangeMsgsCameFromNetwork(); } + lemma HonestPreservesOneViewChangeMessageFromReplicaPerView(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures OneViewChangeMessageFromReplicaPerView(c, v') + { + reveal_HonestReplicasLeaveViewsBehind(); + reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); + reveal_OneViewChangeMessageFromReplicaPerView(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); + } + // This is part of a contradiction proof lemma DoubleAgentDidNotCommit(c: Constants, v:Variables, @@ -1624,35 +1649,44 @@ module Proof { var h_c := c.hosts[step.id].replicaConstants; var h_v' := v'.hosts[step.id].replicaVariables; reveal_TemporarilyDisableCheckpointing(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); + reveal_OneViewChangeMessageFromReplicaPerView(); if(h_step.LeaveViewStep?) { assume false; } else if(h_step.SendCommitStep?) { - assume false; + assert forall vcMsg + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); + if(commitMsg !in v.network.sentMsgs) { + //assume false; + Messages.reveal_UniqueSenders(); + var seqID := commitMsg.payload.seqID; + var workingWindowPreparesRecvd := set key | key in h_v.workingWindow.preparesRcvd[seqID] + :: h_v.workingWindow.preparesRcvd[seqID][key]; + assert |workingWindowPreparesRecvd| == |h_v.workingWindow.preparesRcvd[seqID]|; + assert Messages.PreparedCertificate(workingWindowPreparesRecvd).validFull(c.clusterConfig, seqID); + } } else if(h_step.RecvPrepareStep?) { + assert forall vcMsg + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); assume false; } else if(h_step.PerformStateTransferStep?) { - assume false; - } else if(h_step.SendViewChangeMsgStep?) { - assume false; - } else if(h_step.RecvPrePrepareStep?) { + assert forall vcMsg + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); assume false; } else if(h_step.AdvanceWorkingWindowStep?) { + assert forall vcMsg + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); assume false; - } else if(h_step.RecvCommitStep?) { - assert h_v.workingWindow.getActiveSequenceIDs(h_c) == h_v'.workingWindow.getActiveSequenceIDs(h_c); - assert Replica.RecvCommit(h_c, h_v, h_v', step.msgOps); - assert h_v'.workingWindow == - h_v.workingWindow.(commitsRcvd := h_v'.workingWindow.commitsRcvd); - assert h_v.workingWindow.preparesRcvd == h_v'.workingWindow.preparesRcvd; - var workingWindowPreparesRecvd := set key | key in h_v.workingWindow.preparesRcvd[commitMsg.payload.seqID] - :: h_v.workingWindow.preparesRcvd[commitMsg.payload.seqID][key]; - var workingWindowPreparesRecvd' := set key | key in h_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID] - :: h_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID][key]; - assert workingWindowPreparesRecvd == workingWindowPreparesRecvd'; - assert Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID) == - Replica.ExtractCertificateForSeqID(h_c, h_v', commitMsg.payload.seqID); - } else { + } else if(h_step.RecvViewChangeMsgStep?) { assume false; + } else { + assert forall vcMsg + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); } } @@ -1685,6 +1719,16 @@ module Proof { // reveal_TemporarilyDisableCheckpointing(); } + lemma todoLemma(h_c:Replica.Constants, + h_v:Replica.Variables, + h_v':Replica.Variables, + commitMsg:Message) + ensures Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID) == + Replica.ExtractCertificateForSeqID(h_c, h_v', commitMsg.payload.seqID) + { + + } + lemma HonestPreservesRecordedViewChangeMsgsAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) From b476f58015632549b867c3ac0f09da29e16ab4ff Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Fri, 10 Mar 2023 20:20:37 +0200 Subject: [PATCH 17/38] Debugging session. --- docs/sbft-formal-model/proof.dfy | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 50924290ae..139dee4e6e 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -673,6 +673,19 @@ module Proof { } } + lemma CountPrepareMessagesInWorkingWindow(c:Constants, v:Variables, replicaId:HostId, seqID:Messages.SequenceID) + requires v.WF(c) + requires RecordedPreparesHaveValidSenderID(c, v) + requires IsHonestReplica(c, replicaId) + ensures |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd.Values| == |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd.Keys| + { + reveal_RecordedPreparesHaveValidSenderID(); + CountPrepareMessages(v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID]); + // assert forall sender | && sender in v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd + // :: && c.clusterConfig.IsReplica(sender) + // && v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; + } + lemma HonestPreservesEveryPrepareClientOpMatchesRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1663,7 +1676,16 @@ module Proof { var seqID := commitMsg.payload.seqID; var workingWindowPreparesRecvd := set key | key in h_v.workingWindow.preparesRcvd[seqID] :: h_v.workingWindow.preparesRcvd[seqID][key]; - assert |workingWindowPreparesRecvd| == |h_v.workingWindow.preparesRcvd[seqID]|; + reveal_RecordedPreparesHaveValidSenderID(); + assert RecordedPreparesHaveValidSenderID(c, v); + assert IsHonestReplica(c, step.id); + assert var prepareMap := v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd; + seqID in prepareMap; + assert forall sender | && sender in v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd + :: && c.clusterConfig.IsReplica(sender) + && v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; + CountPrepareMessages(h_v.workingWindow.preparesRcvd[seqID]); + assert |workingWindowPreparesRecvd| <= |h_v.workingWindow.preparesRcvd[seqID].Keys|; assert Messages.PreparedCertificate(workingWindowPreparesRecvd).validFull(c.clusterConfig, seqID); } } else if(h_step.RecvPrepareStep?) { From ca863a18e06976d771d8b8fc35d29eef517655e6 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 13 Mar 2023 21:51:09 +0200 Subject: [PATCH 18/38] Prove for HonestPreservesEveryCommitMsgIsRememberedByItsSenderForCommitStep. --- docs/sbft-formal-model/proof.dfy | 97 +++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 139dee4e6e..4a829dfae6 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -677,10 +677,16 @@ module Proof { requires v.WF(c) requires RecordedPreparesHaveValidSenderID(c, v) requires IsHonestReplica(c, replicaId) - ensures |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd.Values| == |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd.Keys| + requires seqID in v.hosts[replicaId].replicaVariables.workingWindow.getActiveSequenceIDs(c.hosts[replicaId].replicaConstants) + ensures |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID].Values| + == |v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID].Keys| { reveal_RecordedPreparesHaveValidSenderID(); - CountPrepareMessages(v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID]); + var proofSet := v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID]; + forall sender | sender in proofSet.Keys ensures proofSet[sender].sender == sender { + assert c.clusterConfig.IsReplica(sender); + } + CountPrepareMessages(proofSet); // assert forall sender | && sender in v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd // :: && c.clusterConfig.IsReplica(sender) // && v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; @@ -1614,6 +1620,93 @@ module Proof { assume false; } + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForCommitStep( + c:Constants, + v:Variables, + v':Variables, + step:Step, + stepper_v:Replica.Variables, + h_step:Replica.Step, + commitMsg:Message, + committer_c:Replica.Constants, + committer_v':Replica.Variables) returns (certificate:Messages.PreparedCertificate) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, stepper_v, h_step) + requires commitMsg in v'.network.sentMsgs + requires commitMsg.payload.Commit? + requires IsHonestReplica(c, commitMsg.sender) + requires committer_c == c.hosts[commitMsg.sender].replicaConstants + requires committer_v' == v'.hosts[commitMsg.sender].replicaVariables + requires commitMsg.payload.seqID in committer_v'.workingWindow.getActiveSequenceIDs(committer_c) + requires h_step.SendCommitStep? + ensures certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID) + ensures certificate.valid(c.clusterConfig, commitMsg.payload.seqID) + ensures !certificate.empty() + ensures certificate.prototype().view >= commitMsg.payload.view + ensures (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + { + reveal_EveryCommitMsgIsRememberedByItsSender(); + reveal_TemporarilyDisableCheckpointing(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); + reveal_OneViewChangeMessageFromReplicaPerView(); + + certificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); + + // assert forall vcMsg + // :: Replica.IsRecordedViewChangeMsgForView(committer_c, committer_v', committer_v'.view, vcMsg) == + // Replica.IsRecordedViewChangeMsgForView(committer_c, stepper_v, stepper_v.view, vcMsg); + if(commitMsg !in v.network.sentMsgs) { + //assume false; + assert commitMsg.sender == step.id; // Committer and stepper are same in this branch + Messages.reveal_UniqueSenders(); + var seqID := commitMsg.payload.seqID; + var workingWindowPreparesRecvd := set key | key in stepper_v.workingWindow.preparesRcvd[seqID] // !!! TODO: We probably don't want the stepper here + :: stepper_v.workingWindow.preparesRcvd[seqID][key]; + reveal_RecordedPreparesHaveValidSenderID(); + reveal_RecordedPreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); + reveal_RecordedPreparesMatchHostView(); + assert RecordedPreparesHaveValidSenderID(c, v); + assert IsHonestReplica(c, step.id); + assert var prepareMap := v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd; + seqID in prepareMap; + assert forall sender | && sender in v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID] + :: && c.clusterConfig.IsReplica(sender) + && v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; + CountPrepareMessages(stepper_v.workingWindow.preparesRcvd[seqID]); + assert workingWindowPreparesRecvd == stepper_v.workingWindow.preparesRcvd[seqID].Values; // Extentionality + assert |workingWindowPreparesRecvd| <= |stepper_v.workingWindow.preparesRcvd[seqID].Keys|; + assert certificate == Messages.PreparedCertificate(workingWindowPreparesRecvd); + //view:ViewNum, seqID:SequenceID, operationWrapper:OperationWrapper + assert forall m1, m2 | && m1 in certificate.votes + && m2 in certificate.votes + :: && m1.payload.view == m2.payload.view; + assert forall m1, m2 | && m1 in certificate.votes + && m2 in certificate.votes + :: && m1.payload.operationWrapper == m2.payload.operationWrapper; + assert forall m1, m2 | && m1 in certificate.votes + && m2 in certificate.votes + :: && m1.payload.seqID == m2.payload.seqID; + assert certificate.validFull(c.clusterConfig, seqID); + + + assert certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + assert !certificate.empty(); + assert certificate.prototype().view >= commitMsg.payload.view; + assert (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper); + } else { + assert certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + assert !certificate.empty(); + assert certificate.prototype().view >= commitMsg.payload.view; + assert (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper); + } + } + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSender(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) From 8a50c2f08f0db60c49b3fb16f1ce3d01d2d26261 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 13 Mar 2023 22:05:10 +0200 Subject: [PATCH 19/38] Proof cleanup - removed debugging code. --- docs/sbft-formal-model/proof.dfy | 46 +++++++------------------------- 1 file changed, 10 insertions(+), 36 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 4a829dfae6..5b1d90c138 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1657,7 +1657,6 @@ module Proof { // :: Replica.IsRecordedViewChangeMsgForView(committer_c, committer_v', committer_v'.view, vcMsg) == // Replica.IsRecordedViewChangeMsgForView(committer_c, stepper_v, stepper_v.view, vcMsg); if(commitMsg !in v.network.sentMsgs) { - //assume false; assert commitMsg.sender == step.id; // Committer and stepper are same in this branch Messages.reveal_UniqueSenders(); var seqID := commitMsg.payload.seqID; @@ -1667,43 +1666,8 @@ module Proof { reveal_RecordedPreparesRecvdCameFromNetwork(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); reveal_RecordedPreparesMatchHostView(); - assert RecordedPreparesHaveValidSenderID(c, v); - assert IsHonestReplica(c, step.id); - assert var prepareMap := v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd; - seqID in prepareMap; - assert forall sender | && sender in v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID] - :: && c.clusterConfig.IsReplica(sender) - && v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; CountPrepareMessages(stepper_v.workingWindow.preparesRcvd[seqID]); assert workingWindowPreparesRecvd == stepper_v.workingWindow.preparesRcvd[seqID].Values; // Extentionality - assert |workingWindowPreparesRecvd| <= |stepper_v.workingWindow.preparesRcvd[seqID].Keys|; - assert certificate == Messages.PreparedCertificate(workingWindowPreparesRecvd); - //view:ViewNum, seqID:SequenceID, operationWrapper:OperationWrapper - assert forall m1, m2 | && m1 in certificate.votes - && m2 in certificate.votes - :: && m1.payload.view == m2.payload.view; - assert forall m1, m2 | && m1 in certificate.votes - && m2 in certificate.votes - :: && m1.payload.operationWrapper == m2.payload.operationWrapper; - assert forall m1, m2 | && m1 in certificate.votes - && m2 in certificate.votes - :: && m1.payload.seqID == m2.payload.seqID; - assert certificate.validFull(c.clusterConfig, seqID); - - - assert certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); - assert !certificate.empty(); - assert certificate.prototype().view >= commitMsg.payload.view; - assert (certificate.prototype().view == commitMsg.payload.view - ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper); - } else { - assert certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); - assert !certificate.empty(); - assert certificate.prototype().view >= commitMsg.payload.view; - assert (certificate.prototype().view == commitMsg.payload.view - ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper); } } @@ -1760,6 +1724,16 @@ module Proof { if(h_step.LeaveViewStep?) { assume false; } else if(h_step.SendCommitStep?) { + var res := HonestPreservesEveryCommitMsgIsRememberedByItsSenderForCommitStep( + c, + v, + v', + step, + h_v, + h_step, + commitMsg, + h_c, + h_v'); assert forall vcMsg :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); From f081492777309209df212bbd58cce210b3459f66 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 15 Mar 2023 21:06:03 +0200 Subject: [PATCH 20/38] Added lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep. --- docs/sbft-formal-model/proof.dfy | 89 ++++++++++++++++++++++---------- 1 file changed, 62 insertions(+), 27 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 5b1d90c138..75c03957c1 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1671,6 +1671,56 @@ module Proof { } } + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep( + c:Constants, + v:Variables, + v':Variables, + step:Step, + stepper_v:Replica.Variables, + h_step:Replica.Step, + commitMsg:Message, + committer_c:Replica.Constants, + committer_v':Replica.Variables) returns (certificate:Messages.PreparedCertificate) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, stepper_v, h_step) + requires commitMsg in v'.network.sentMsgs + requires commitMsg.payload.Commit? + requires IsHonestReplica(c, commitMsg.sender) + requires committer_c == c.hosts[commitMsg.sender].replicaConstants + requires committer_v' == v'.hosts[commitMsg.sender].replicaVariables + requires commitMsg.payload.seqID in committer_v'.workingWindow.getActiveSequenceIDs(committer_c) + requires h_step.RecvPrepareStep? + ensures certificate == Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID) + ensures certificate.valid(c.clusterConfig, commitMsg.payload.seqID) + ensures !certificate.empty() + ensures certificate.prototype().view >= commitMsg.payload.view + ensures (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + { + reveal_EveryCommitMsgIsRememberedByItsSender(); + reveal_TemporarilyDisableCheckpointing(); + reveal_RecordedViewChangeMsgsCameFromNetwork(); + reveal_OneViewChangeMessageFromReplicaPerView(); + + assert EveryCommitMsgIsRememberedByItsSender(c, v); + + var committer_v := v.hosts[commitMsg.sender].replicaVariables; + + var oldCertificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v, commitMsg.payload.seqID); + certificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); + + if(commitMsg !in v.network.sentMsgs) { + assert false; + } + assert Replica.PrepareProofSetWF(committer_c, committer_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID]); + + assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert oldCertificate.votes <= certificate.votes; + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + assume false; + } + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSender(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1732,34 +1782,19 @@ module Proof { h_v, h_step, commitMsg, - h_c, - h_v'); - assert forall vcMsg - :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == - Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); - if(commitMsg !in v.network.sentMsgs) { - //assume false; - Messages.reveal_UniqueSenders(); - var seqID := commitMsg.payload.seqID; - var workingWindowPreparesRecvd := set key | key in h_v.workingWindow.preparesRcvd[seqID] - :: h_v.workingWindow.preparesRcvd[seqID][key]; - reveal_RecordedPreparesHaveValidSenderID(); - assert RecordedPreparesHaveValidSenderID(c, v); - assert IsHonestReplica(c, step.id); - assert var prepareMap := v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd; - seqID in prepareMap; - assert forall sender | && sender in v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd - :: && c.clusterConfig.IsReplica(sender) - && v.hosts[step.id].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; - CountPrepareMessages(h_v.workingWindow.preparesRcvd[seqID]); - assert |workingWindowPreparesRecvd| <= |h_v.workingWindow.preparesRcvd[seqID].Keys|; - assert Messages.PreparedCertificate(workingWindowPreparesRecvd).validFull(c.clusterConfig, seqID); - } + c.hosts[commitMsg.sender].replicaConstants, + v'.hosts[commitMsg.sender].replicaVariables); } else if(h_step.RecvPrepareStep?) { - assert forall vcMsg - :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == - Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); - assume false; + var res := HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep( + c, + v, + v', + step, + h_v, + h_step, + commitMsg, + c.hosts[commitMsg.sender].replicaConstants, + v'.hosts[commitMsg.sender].replicaVariables); } else if(h_step.PerformStateTransferStep?) { assert forall vcMsg :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == From c0af5bd5430ab934a25c27d84475ff8786232ecc Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Fri, 17 Mar 2023 10:50:48 +0200 Subject: [PATCH 21/38] In progress debugging of HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep. Refactoring in replica.i.dfy to improve triggering by dafny's heuristics. --- docs/sbft-formal-model/proof.dfy | 76 ++++++++++++++++++++++++---- docs/sbft-formal-model/replica.i.dfy | 43 +++++++++------- 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 75c03957c1..1efcfb42db 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1671,6 +1671,19 @@ module Proof { } } + lemma FullQuorumOfPreparesEnsuresValidFullCertificate( + c:Constants, + v:Variables, + h_c:Replica.Constants, + h_v:Replica.Variables, + seqID:Messages.SequenceID) + requires v.WF(c) + requires |Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).votes| >= h_c.clusterConfig.AgreementQuorum() + ensures Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).validFull(h_c.clusterConfig, seqID) + { + + } + lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep( c:Constants, v:Variables, @@ -1701,6 +1714,9 @@ module Proof { reveal_TemporarilyDisableCheckpointing(); reveal_RecordedViewChangeMsgsCameFromNetwork(); reveal_OneViewChangeMessageFromReplicaPerView(); + reveal_RecordedPreparesMatchHostView(); + reveal_RecordedPreparesRecvdCameFromNetwork(); + reveal_RecordedPreparesClientOpsMatchPrePrepare(); assert EveryCommitMsgIsRememberedByItsSender(c, v); @@ -1714,11 +1730,49 @@ module Proof { } assert Replica.PrepareProofSetWF(committer_c, committer_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID]); - assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - assert oldCertificate.votes <= certificate.votes; - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); - assume false; + if(step.id == commitMsg.sender) { + if(commitMsg.payload.view < committer_v.view) { + assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + + if(oldCertificate.votes == Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID)) { + assert committer_v'.viewChangeMsgsRecvd == committer_v.viewChangeMsgsRecvd; + + assert Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID) == + Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v', commitMsg.payload.seqID); + + assert |Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v', commitMsg.payload.seqID)| >= + committer_c.clusterConfig.AgreementQuorum(); + if(|Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)| >= + committer_c.clusterConfig.AgreementQuorum()) { + assert certificate == Messages.PreparedCertificate(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)); + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + } else { + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + } + } else { + assert oldCertificate.votes == Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID); + assert Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID) <= + Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID); + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + } + + } else { + assume false; + assert Replica.ExtractCertificateForSeqID(committer_c, committer_v, commitMsg.payload.seqID).votes == + Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID); + assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert oldCertificate.votes <= certificate.votes; + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + } + + assume false; + } else { + assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert oldCertificate.votes <= certificate.votes; + assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + } } lemma HonestPreservesEveryCommitMsgIsRememberedByItsSender(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1797,20 +1851,20 @@ module Proof { v'.hosts[commitMsg.sender].replicaVariables); } else if(h_step.PerformStateTransferStep?) { assert forall vcMsg - :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == - Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v'.viewChangeMsgsRecvd, h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v.viewChangeMsgsRecvd, h_v.view, vcMsg); assume false; } else if(h_step.AdvanceWorkingWindowStep?) { assert forall vcMsg - :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == - Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v'.viewChangeMsgsRecvd, h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v.viewChangeMsgsRecvd, h_v.view, vcMsg); assume false; } else if(h_step.RecvViewChangeMsgStep?) { assume false; } else { assert forall vcMsg - :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v', h_v'.view, vcMsg) == - Replica.IsRecordedViewChangeMsgForView(h_c, h_v, h_v.view, vcMsg); + :: Replica.IsRecordedViewChangeMsgForView(h_c, h_v'.viewChangeMsgsRecvd, h_v'.view, vcMsg) == + Replica.IsRecordedViewChangeMsgForView(h_c, h_v.viewChangeMsgsRecvd, h_v.view, vcMsg); } } diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index d7c992c9dd..9b4851b066 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -515,7 +515,7 @@ module Replica { newView, v.workingWindow.lastStableCheckpoint, CheckpointsQuorum(ExtractStableCheckpointProof(c, v)), - ExtractCertificatesFromWorkingWindow(c, v))); + ExtractCertificates(c, v))); // TODO: this should follow from the invariant and from the way we collect prepares. // && (forall seqID :: seqID in vcMsg.payload.certificates ==> // (vcMsg.payload.certificates[seqID].valid(c.clusterConfig.AgreementQuorum()))) @@ -535,33 +535,41 @@ module Replica { && msg.payload.committedClientOperations == stateUpToSeqID } - predicate IsRecordedViewChangeMsgForView(c:Constants, v:Variables, view:ViewNum, viewChangeMsg:Network.Message) + predicate IsRecordedViewChangeMsgForView(c:Constants, viewChangeMsgsRecvd:ViewChangeMsgs, view:ViewNum, viewChangeMsg:Network.Message) { - && v.WF(c) + && c.WF() && viewChangeMsg.payload.ViewChangeMsg? - && viewChangeMsg in v.viewChangeMsgsRecvd.msgs + && viewChangeMsg in viewChangeMsgsRecvd.msgs && viewChangeMsg.payload.newView == view && viewChangeMsg.sender == c.myId } - function GetViewChangeMsgForView(c:Constants, v:Variables, view:ViewNum) : Network.Message - requires v.WF(c) - requires exists viewChangeMsg :: IsRecordedViewChangeMsgForView(c, v, view, viewChangeMsg) + function GetViewChangeMsgForView(c:Constants, viewChangeMsgsRecvd:ViewChangeMsgs, view:ViewNum) : Network.Message + requires c.WF() + requires exists viewChangeMsg :: IsRecordedViewChangeMsgForView(c, viewChangeMsgsRecvd, view, viewChangeMsg) { - var viewChangeMsg :| IsRecordedViewChangeMsgForView(c, v, view, viewChangeMsg); + var viewChangeMsg :| IsRecordedViewChangeMsgForView(c, viewChangeMsgsRecvd, view, viewChangeMsg); viewChangeMsg } - function ExtractPreparedCertificateFromLatestViewChangeMsg(c:Constants, v:Variables, seqID:SequenceID) : PreparedCertificate + function ExtractPreparesFromWorkingWindow(c:Constants, v:Variables, seqID:SequenceID) : set> + requires v.WF(c) + requires seqID in v.workingWindow.getActiveSequenceIDs(c) + { + set key | key in v.workingWindow.preparesRcvd[seqID] // This set comprehension is the same as calling .Values, + :: v.workingWindow.preparesRcvd[seqID][key] // but this way it is better for triggering in Dafny + } + + function ExtractPreparesFromLatestViewChangeMsg(c:Constants, v:Variables, seqID:SequenceID) : set> { - if !exists vcMsg :: IsRecordedViewChangeMsgForView(c, v, v.view, vcMsg) - then PreparedCertificate({}) - else if seqID !in GetViewChangeMsgForView(c, v, v.view).payload.certificates - then PreparedCertificate({}) - else GetViewChangeMsgForView(c, v, v.view).payload.certificates[seqID] + if !exists vcMsg :: IsRecordedViewChangeMsgForView(c, v.viewChangeMsgsRecvd, v.view, vcMsg) + then {} + else if seqID !in GetViewChangeMsgForView(c, v.viewChangeMsgsRecvd, v.view).payload.certificates + then {} + else GetViewChangeMsgForView(c, v.viewChangeMsgsRecvd, v.view).payload.certificates[seqID].votes } - function ExtractCertificatesFromWorkingWindow(c:Constants, v:Variables) : map + function ExtractCertificates(c:Constants, v:Variables) : map requires v.WF(c) { map seqID | seqID in v.workingWindow.getActiveSequenceIDs(c) :: ExtractCertificateForSeqID(c, v, seqID) @@ -571,9 +579,8 @@ module Replica { requires v.WF(c) requires seqID in v.workingWindow.getActiveSequenceIDs(c) { - var workingWindowPreparesRecvd := set key | key in v.workingWindow.preparesRcvd[seqID] // This set comprehension is the same as calling .Values, - :: v.workingWindow.preparesRcvd[seqID][key];// but this way it is better for triggering in Dafny - var viewChangeMsgPreparesRecvd := ExtractPreparedCertificateFromLatestViewChangeMsg(c, v, seqID).votes; + var workingWindowPreparesRecvd := ExtractPreparesFromWorkingWindow(c, v, seqID); + var viewChangeMsgPreparesRecvd := ExtractPreparesFromLatestViewChangeMsg(c, v, seqID); if |workingWindowPreparesRecvd| >= c.clusterConfig.AgreementQuorum() then PreparedCertificate(workingWindowPreparesRecvd) else if |viewChangeMsgPreparesRecvd| >= c.clusterConfig.AgreementQuorum() From 3aef91fb1ad801fb9f1627a18a64eb2ef686f4cb Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Tue, 21 Mar 2023 19:09:53 +0200 Subject: [PATCH 22/38] Proof for certificate.valid complete in case commitMsg.payload.view < committer_v.view. --- docs/sbft-formal-model/proof.dfy | 35 +++++++++++--------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 1efcfb42db..e41dad9a70 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1717,6 +1717,7 @@ module Proof { reveal_RecordedPreparesMatchHostView(); reveal_RecordedPreparesRecvdCameFromNetwork(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); + Messages.reveal_UniqueSenders(); assert EveryCommitMsgIsRememberedByItsSender(c, v); @@ -1732,32 +1733,20 @@ module Proof { if(step.id == commitMsg.sender) { if(commitMsg.payload.view < committer_v.view) { - assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - - if(oldCertificate.votes == Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID)) { - assert committer_v'.viewChangeMsgsRecvd == committer_v.viewChangeMsgsRecvd; - - assert Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID) == - Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v', commitMsg.payload.seqID); - - assert |Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v', commitMsg.payload.seqID)| >= - committer_c.clusterConfig.AgreementQuorum(); - if(|Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)| >= - committer_c.clusterConfig.AgreementQuorum()) { - assert certificate == Messages.PreparedCertificate(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)); - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - } else { - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + if(oldCertificate.votes != Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID)) { + forall prepare | prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID) + ensures prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID) { + assert committer_v.workingWindow.preparesRcvd[commitMsg.payload.seqID][prepare.sender] == + committer_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID][prepare.sender]; //Trigger } - } else { - assert oldCertificate.votes == Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID); - assert Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID) <= - Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID); - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + SubsetCardinality(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID), + Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)); } - + assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); } else { - assume false; + assert commitMsg.payload.view == committer_v.view by { + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + } assert Replica.ExtractCertificateForSeqID(committer_c, committer_v, commitMsg.payload.seqID).votes == Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID); assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); From 497dd6805f69e41ab6d7dd3af0d4dc370d1ed9c2 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 22 Mar 2023 22:05:24 +0200 Subject: [PATCH 23/38] Progress on proving HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep. --- docs/sbft-formal-model/messages.dfy | 8 +- docs/sbft-formal-model/proof.dfy | 160 +++++++++++++++++----------- 2 files changed, 107 insertions(+), 61 deletions(-) diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index 3699402a97..b0d312b102 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -60,6 +60,11 @@ module Messages { predicate empty() { && |votes| == 0 } + predicate votesRespectView(view:ViewNum) + requires WF() + { + !empty() ==> prototype().view < view + } } datatype ViewChangeMsgsSelectedByPrimary = ViewChangeMsgsSelectedByPrimary(msgs:set>) { @@ -130,7 +135,8 @@ module Messages { requires ViewChangeMsg? { && (forall seqID | seqID in certificates - :: certificates[seqID].valid(clusterConfig, seqID)) + :: && certificates[seqID].valid(clusterConfig, seqID) + && certificates[seqID].votesRespectView(newView)) && proofForLastStable.valid(lastStableCheckpoint, clusterConfig.AgreementQuorum()) } predicate validNewViewMsg(clusterConfig:ClusterConfig.Constants) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index e41dad9a70..0fa4ecfbe4 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -74,7 +74,7 @@ module Proof { :: RecordedPreparesRecvdCameFromNetworkForHost(c, v, observer)) } - predicate {:opaque} EveryCommitMsgIsSupportedByAQuorumOfPrepares(c:Constants, v:Variables) { + predicate {:opaque} EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c:Constants, v:Variables) { && v.WF(c) && (forall commitMsg | && commitMsg in v.network.sentMsgs && commitMsg.payload.Commit? @@ -83,6 +83,20 @@ module Proof { commitMsg.payload.seqID, commitMsg.payload.operationWrapper) ) } + predicate {:opaque} EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c:Constants, v:Variables) { + && v.WF(c) + && (forall commitMsg | && commitMsg in v.network.sentMsgs + && commitMsg.payload.Commit? + && IsHonestReplica(c, commitMsg.sender) + && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v := v.hosts[commitMsg.sender].replicaVariables; + && h_v.view == commitMsg.payload.view + && commitMsg.payload.seqID in h_v.workingWindow.getActiveSequenceIDs(h_c) + :: && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v := v.hosts[commitMsg.sender].replicaVariables; + && |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum()) + } + predicate {:opaque} EveryCommitMsgIsRememberedByItsSender(c:Constants, v:Variables) { //TODO: this does not cover Checkpointing && v.WF(c) && (forall commitMsg | && commitMsg in v.network.sentMsgs @@ -337,7 +351,7 @@ module Proof { && RecordedPreparesRecvdCameFromNetwork(c, v) && RecordedPrePreparesMatchHostView(c, v) && RecordedPreparesMatchHostView(c, v) - && EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v) + && EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v) && RecordedPreparesClientOpsMatchPrePrepare(c, v) && RecordedCommitsClientOpsMatchPrePrepare(c, v) && EverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v) @@ -404,7 +418,7 @@ module Proof { reveal_RecordedPreparesMatchHostView(); reveal_RecordedPreparesClientOpsMatchPrePrepare(); reveal_HonestReplicasLockOnPrepareForGivenView(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); var prepares1 := sentPreparesForSeqID(c, v, msg1.payload.view, msg1.payload.seqID, msg1.payload.operationWrapper); var senders1 := Messages.sendersOf(prepares1); @@ -589,13 +603,13 @@ module Proof { } } - lemma AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c: Constants, v:Variables, v':Variables, step:Step) + lemma AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c: Constants, v:Variables, v':Variables, step:Step) requires Inv(c, v) requires NextStep(c, v, v', step) - ensures EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v') + ensures EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v') { - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); - // A proof of EveryCommitMsgIsSupportedByAQuorumOfPrepares, + reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); + // A proof of EveryCommitMsgIsSupportedByAQuorumOfSentPrepares, // by selecting an arbitrary commitMsg instance forall commitMsg | && commitMsg in v'.network.sentMsgs && commitMsg.payload.Commit? @@ -932,7 +946,7 @@ module Proof { // reveal_CommitMsgsFromHonestSendersAgree(); // reveal_RecordedCheckpointsRecvdCameFromNetwork(); // reveal_HonestReplicasLockOnCommitForGivenView(); - // reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + // reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); // h_v.workingWindow.reveal_Shift(); reveal_UnCommitableAgreesWithRecordedPrePrepare(); reveal_RecordedPrePreparesMatchHostView(); @@ -1193,21 +1207,22 @@ module Proof { // // certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper // ) } - + //TODO: refactor predicate {:opaque} SentViewChangesMsgsComportWithUncommitableInView(c:Constants, v:Variables) { - && v.WF(c) - && (forall viewChangeMsg, - seqID:Messages.SequenceID, - view:nat, - operationWrapper:Messages.OperationWrapper | - && viewChangeMsg in v.network.sentMsgs - && viewChangeMsg.payload.ViewChangeMsg? - && IsHonestReplica(c, viewChangeMsg.sender) - && view < viewChangeMsg.payload.view - && var certificate := viewChangeMsg.payload.certificates[seqID]; - && !certificate.empty() - && operationWrapper != certificate.prototype().operationWrapper - :: && UnCommitableInView(c, v, seqID, view, operationWrapper)) + && true + // && v.WF(c) + // && (forall viewChangeMsg, + // seqID:Messages.SequenceID, + // view:nat, + // operationWrapper:Messages.OperationWrapper | + // && viewChangeMsg in v.network.sentMsgs + // && viewChangeMsg.payload.ViewChangeMsg? + // && IsHonestReplica(c, viewChangeMsg.sender) + // && view < viewChangeMsg.payload.newView + // && var certificate := viewChangeMsg.payload.certificates[seqID]; + // && !certificate.empty() + // && operationWrapper != certificate.prototype().operationWrapper + // :: && UnCommitableInView(c, v, seqID, view, operationWrapper)) } //TODO: write a predicate Prepare matches Commit @@ -1224,7 +1239,7 @@ module Proof { ensures prepare.payload.seqID == commitMsg.payload.seqID ensures prepare.payload.operationWrapper == commitMsg.payload.operationWrapper { - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); var prepares := sentPreparesForSeqID(c, v, commitMsg.payload.view, commitMsg.payload.seqID, commitMsg.payload.operationWrapper); prepare := GetMsgFromHonestSender(c, v, prepares); } @@ -1607,7 +1622,7 @@ module Proof { reveal_CommitMsgsFromHonestSendersAgree(); reveal_RecordedCheckpointsRecvdCameFromNetwork(); reveal_HonestReplicasLockOnCommitForGivenView(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); h_v.workingWindow.reveal_Shift(); } @@ -1719,48 +1734,83 @@ module Proof { reveal_RecordedPreparesClientOpsMatchPrePrepare(); Messages.reveal_UniqueSenders(); + var seqID := commitMsg.payload.seqID; + assert EveryCommitMsgIsRememberedByItsSender(c, v); var committer_v := v.hosts[commitMsg.sender].replicaVariables; - var oldCertificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v, commitMsg.payload.seqID); - certificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v', commitMsg.payload.seqID); + var oldCertificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v, seqID); + certificate := Replica.ExtractCertificateForSeqID(committer_c, committer_v', seqID); if(commitMsg !in v.network.sentMsgs) { assert false; } - assert Replica.PrepareProofSetWF(committer_c, committer_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID]); + assert Replica.PrepareProofSetWF(committer_c, committer_v'.workingWindow.preparesRcvd[seqID]); if(step.id == commitMsg.sender) { if(commitMsg.payload.view < committer_v.view) { - if(oldCertificate.votes != Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID)) { - forall prepare | prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID) - ensures prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID) { - assert committer_v.workingWindow.preparesRcvd[commitMsg.payload.seqID][prepare.sender] == - committer_v'.workingWindow.preparesRcvd[commitMsg.payload.seqID][prepare.sender]; //Trigger + if(oldCertificate.votes != Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, seqID)) { + forall prepare | prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID) + ensures prepare in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID) { + assert committer_v.workingWindow.preparesRcvd[seqID][prepare.sender] == + committer_v'.workingWindow.preparesRcvd[seqID][prepare.sender]; //Trigger } - SubsetCardinality(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID), - Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)); + SubsetCardinality(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID), + Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID)); } - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + assert certificate.valid(c.clusterConfig, seqID); + assert (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper); } else { assert commitMsg.payload.view == committer_v.view by { reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); } - assert Replica.ExtractCertificateForSeqID(committer_c, committer_v, commitMsg.payload.seqID).votes == - Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, commitMsg.payload.seqID); - assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - assert oldCertificate.votes <= certificate.votes; - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + var oldPrepares := Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID); + var newPrepares := Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID); + if(|newPrepares| >= c.clusterConfig.AgreementQuorum()) { + assert certificate.validFull(c.clusterConfig, seqID); + if |oldPrepares| >= c.clusterConfig.AgreementQuorum() { + assert certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper; + } else { + reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); + assume EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v); + assert false; // Couldn't have sent a Commit + } + } else { + if (|oldPrepares| >= c.clusterConfig.AgreementQuorum()) { + forall p | p in oldPrepares ensures p in newPrepares { + assert committer_v'.workingWindow.preparesRcvd[seqID][p.sender] == p; // Trigger + } + SubsetCardinality(oldPrepares,newPrepares); + } + if |Replica.ExtractPreparesFromLatestViewChangeMsg(committer_c, committer_v, seqID)| >= c.clusterConfig.AgreementQuorum() { + var viewChangeMsg := Replica.GetViewChangeMsgForView(committer_c, committer_v.viewChangeMsgsRecvd, committer_v.view); + var viewChangeMsgVotes := viewChangeMsg.payload.certificates[seqID]; + assert viewChangeMsg.payload.newView == committer_v.view; + assert viewChangeMsg.payload.validViewChangeMsg(c.clusterConfig) by { + reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); + } + assert viewChangeMsgVotes.prototype().view < committer_v.view; + + assert false; + } + assert oldCertificate.validFull(c.clusterConfig, seqID); + assert oldCertificate.votes == Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID); + assert certificate.validFull(c.clusterConfig, seqID); + assert certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper; + } + assert certificate.valid(c.clusterConfig, seqID); + assert certificate.prototype().view == commitMsg.payload.view; + assert certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper; } - assume false; + //assume false; } else { - assert oldCertificate.validFull(c.clusterConfig, commitMsg.payload.seqID); + assert oldCertificate.validFull(c.clusterConfig, seqID); assert oldCertificate.votes <= certificate.votes; - assert certificate.validFull(c.clusterConfig, commitMsg.payload.seqID); - assert certificate.valid(c.clusterConfig, commitMsg.payload.seqID); + assert certificate.validFull(c.clusterConfig, seqID); + assert certificate.valid(c.clusterConfig, seqID); } } @@ -1864,7 +1914,7 @@ module Proof { // reveal_RecordedPreparesRecvdCameFromNetwork(); // reveal_RecordedPrePreparesMatchHostView(); // reveal_RecordedPreparesMatchHostView(); - // reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + // reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); // reveal_RecordedPreparesClientOpsMatchPrePrepare(); // reveal_RecordedCommitsClientOpsMatchPrePrepare(); // reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); @@ -1886,16 +1936,6 @@ module Proof { // reveal_TemporarilyDisableCheckpointing(); } - lemma todoLemma(h_c:Replica.Constants, - h_v:Replica.Variables, - h_v':Replica.Variables, - commitMsg:Message) - ensures Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID) == - Replica.ExtractCertificateForSeqID(h_c, h_v', commitMsg.payload.seqID) - { - - } - lemma HonestPreservesRecordedViewChangeMsgsAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1944,7 +1984,7 @@ module Proof { HonestPreservesRecordedPrePreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesMatchHostView(c, v, v', step, h_v, h_step); - AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v, v', step); + AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v, v', step); HonestPreservesRecordedPreparesClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesRecordedCommitsClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesEverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v, v', step, h_v, h_step); @@ -2026,8 +2066,8 @@ module Proof { assert HonestReplicasLockOnCommitForGivenView(c, v') by { reveal_HonestReplicasLockOnCommitForGivenView(); } - assert EveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v') by { - AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfPrepares(c, v, v', step); + assert EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v') by { + AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v, v', step); } assert RecordedPrePreparesMatchHostView(c, v') by { reveal_RecordedPrePreparesMatchHostView(); @@ -2082,7 +2122,7 @@ module Proof { reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); reveal_HonestReplicasLockOnPrepareForGivenView(); reveal_RecordedCheckpointsRecvdCameFromNetwork(); - reveal_EveryCommitMsgIsSupportedByAQuorumOfPrepares(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); reveal_RecordedPrePreparesMatchHostView(); reveal_UnCommitableAgreesWithPrepare(); reveal_UnCommitableAgreesWithRecordedPrePrepare(); From 20902765c5edae8208bb6a2f4fce98ff150bfe5e Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 23 Mar 2023 21:07:19 +0200 Subject: [PATCH 24/38] HonestPreservesEveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares proof in progress. --- docs/sbft-formal-model/proof.dfy | 135 ++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 20 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 0fa4ecfbe4..26a2101aa0 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -88,13 +88,13 @@ module Proof { && (forall commitMsg | && commitMsg in v.network.sentMsgs && commitMsg.payload.Commit? && IsHonestReplica(c, commitMsg.sender) - && var h_c := c.hosts[commitMsg.sender].replicaConstants; - && var h_v := v.hosts[commitMsg.sender].replicaVariables; - && h_v.view == commitMsg.payload.view - && commitMsg.payload.seqID in h_v.workingWindow.getActiveSequenceIDs(h_c) - :: && var h_c := c.hosts[commitMsg.sender].replicaConstants; - && var h_v := v.hosts[commitMsg.sender].replicaVariables; - && |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum()) + && var committer_c := c.hosts[commitMsg.sender].replicaConstants; + && var committer_v := v.hosts[commitMsg.sender].replicaVariables; + && committer_v.view == commitMsg.payload.view + && commitMsg.payload.seqID in committer_v.workingWindow.getActiveSequenceIDs(committer_c) + :: && var committer_c := c.hosts[commitMsg.sender].replicaConstants; + && var committer_v := v.hosts[commitMsg.sender].replicaVariables; + && |Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum()) } predicate {:opaque} EveryCommitMsgIsRememberedByItsSender(c:Constants, v:Variables) { //TODO: this does not cover Checkpointing @@ -352,6 +352,7 @@ module Proof { && RecordedPrePreparesMatchHostView(c, v) && RecordedPreparesMatchHostView(c, v) && EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v) + && EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v) && RecordedPreparesClientOpsMatchPrePrepare(c, v) && RecordedCommitsClientOpsMatchPrePrepare(c, v) && EverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v) @@ -646,6 +647,84 @@ module Proof { } } + lemma HonestPreservesEveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + requires Inv(c, v) + requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) + ensures EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v') + { + h_v.workingWindow.reveal_Shift(); + reveal_RecordedPreparesRecvdCameFromNetwork(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); + reveal_TemporarilyDisableCheckpointing(); + reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + if (h_step.SendCommitStep?) { + forall commitMsg | && commitMsg in v'.network.sentMsgs + && commitMsg.payload.Commit? + && IsHonestReplica(c, commitMsg.sender) + && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v' := v'.hosts[commitMsg.sender].replicaVariables; + && h_v'.view == commitMsg.payload.view + && commitMsg.payload.seqID in h_v'.workingWindow.getActiveSequenceIDs(h_c) + ensures && var h_c := c.hosts[commitMsg.sender].replicaConstants; + && var h_v' := v'.hosts[commitMsg.sender].replicaVariables; + && |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum() + { + var h_c := c.hosts[commitMsg.sender].replicaConstants; + var h_v' := v'.hosts[commitMsg.sender].replicaVariables; + if(commitMsg in v.network.sentMsgs) { + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v); + assert |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum(); + } else { + assert Replica.QuorumOfPrepares(h_c, h_v', h_step.seqID); + assert step.id == commitMsg.sender; + CountPrepareMessagesInWorkingWindow(c, v, commitMsg.sender, h_step.seqID); + CountPrepareMessages2(h_c, h_v, h_step.seqID); + assert |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum(); + } + } + + + + + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } else if (h_step.AdvanceWorkingWindowStep?) { + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } else if(h_step.PerformStateTransferStep?) { + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } else if(h_step.RecvPrepareStep?) { + var h_c := c.hosts[step.id].replicaConstants; + var h_v' := v'.hosts[step.id].replicaVariables; + forall commitMsg | && commitMsg in v'.network.sentMsgs + && commitMsg.payload.Commit? + && IsHonestReplica(c, commitMsg.sender) + && var committer_c := c.hosts[commitMsg.sender].replicaConstants; + && var committer_v' := v'.hosts[commitMsg.sender].replicaVariables; + && committer_v'.view == commitMsg.payload.view + && commitMsg.payload.seqID in committer_v'.workingWindow.getActiveSequenceIDs(h_c) + ensures && var committer_c := c.hosts[commitMsg.sender].replicaConstants; + && var committer_v' := v'.hosts[commitMsg.sender].replicaVariables; + && |Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum() + { + var seqID := commitMsg.payload.seqID; + var committer_c := c.hosts[commitMsg.sender].replicaConstants; + var committer_v := v.hosts[commitMsg.sender].replicaVariables; + var committer_v' := v'.hosts[commitMsg.sender].replicaVariables; + if(commitMsg.sender == step.id) { + CountPrepareMessages2(committer_c, committer_v, seqID); + Library.SubsetCardinality(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID), + Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID)); + } else { + assert committer_v == committer_v'; + } + } + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } else if(h_step.LeaveViewStep?) { + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } else { + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); + } + } + lemma HonestPreservesRecordedPreparesClientOpsMatchPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -706,6 +785,12 @@ module Proof { // && v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; } + lemma CountPrepareMessages2(h_c:Replica.Constants, h_v:Replica.Variables, seqID:Messages.SequenceID) + ensures |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, seqID)| == |h_v.workingWindow.preparesRcvd[seqID]| + { + assume false; + } + lemma HonestPreservesEveryPrepareClientOpMatchesRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1506,7 +1591,7 @@ module Proof { requires c.clusterConfig.IsHonestReplica(doubleAgent) ensures !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { - + assume false; } lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1557,12 +1642,12 @@ module Proof { } assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // Need to finish this proof + assume false; // viewchangemessages from honest sender comport with uncommitable in view reveal_SentViewChangesMsgsComportWithUncommitableInView(); assume SentViewChangesMsgsComportWithUncommitableInView(c, v); assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); // Left off here. - // assume false; } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { reveal_HonestReplicasLeaveViewsBehind(); @@ -1686,18 +1771,18 @@ module Proof { } } - lemma FullQuorumOfPreparesEnsuresValidFullCertificate( - c:Constants, - v:Variables, - h_c:Replica.Constants, - h_v:Replica.Variables, - seqID:Messages.SequenceID) - requires v.WF(c) - requires |Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).votes| >= h_c.clusterConfig.AgreementQuorum() - ensures Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).validFull(h_c.clusterConfig, seqID) - { + // lemma FullQuorumOfPreparesEnsuresValidFullCertificate( + // c:Constants, + // v:Variables, + // h_c:Replica.Constants, + // h_v:Replica.Variables, + // seqID:Messages.SequenceID) + // requires v.WF(c) + // requires |Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).votes| >= h_c.clusterConfig.AgreementQuorum() + // ensures Replica.ExtractCertificateForSeqID(h_c, h_v, seqID).validFull(h_c.clusterConfig, seqID) + // { - } + // } lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForRecvPrepareStep( c:Constants, @@ -1985,6 +2070,7 @@ module Proof { HonestPreservesRecordedPreparesRecvdCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesMatchHostView(c, v, v', step, h_v, h_step); AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v, v', step); + HonestPreservesEveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v, v', step, h_v, h_step); HonestPreservesRecordedPreparesClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesRecordedCommitsClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesEverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v, v', step, h_v, h_step); @@ -2005,6 +2091,7 @@ module Proof { HonestPreservesEveryCommitMsgIsRememberedByItsSender(c, v, v', step, h_v, h_step); HonestPreservesViewChangeMsgsFromHonestInNetworkAreValid(c, v, v', step, h_v, h_step); HonestPreservesRecordedViewChangeMsgsAreValid(c, v, v', step, h_v, h_step); + HonestPreservesOneViewChangeMessageFromReplicaPerView(c, v, v', step, h_v, h_step); assume TemporarilyDisableCheckpointing(c, v'); } else { InvNextFaultyOrClient(c, v, v', step); @@ -2069,6 +2156,9 @@ module Proof { assert EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v') by { AlwaysPreservesEveryCommitMsgIsSupportedByAQuorumOfSentPrepares(c, v, v', step); } + assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v') by { + reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); + } assert RecordedPrePreparesMatchHostView(c, v') by { reveal_RecordedPrePreparesMatchHostView(); } @@ -2099,6 +2189,9 @@ module Proof { assert ViewChangeMsgsFromHonestInNetworkAreValid(c, v') by { reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); } + assert OneViewChangeMessageFromReplicaPerView(c, v') by { + reveal_OneViewChangeMessageFromReplicaPerView(); + } assume TemporarilyDisableCheckpointing(c, v'); } @@ -2123,6 +2216,7 @@ module Proof { reveal_HonestReplicasLockOnPrepareForGivenView(); reveal_RecordedCheckpointsRecvdCameFromNetwork(); reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); + reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); reveal_RecordedPrePreparesMatchHostView(); reveal_UnCommitableAgreesWithPrepare(); reveal_UnCommitableAgreesWithRecordedPrePrepare(); @@ -2133,6 +2227,7 @@ module Proof { reveal_EveryCommitMsgIsRememberedByItsSender(); reveal_RecordedViewChangeMsgsAreValid(); reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); + reveal_OneViewChangeMessageFromReplicaPerView(); reveal_TemporarilyDisableCheckpointing(); } } From eff1eda3b213b9caae73a6ee2503b92c18175b85 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 27 Mar 2023 23:08:08 +0300 Subject: [PATCH 25/38] Proof that sets extracted form the Working window have consistent cardinality. --- docs/sbft-formal-model/library/Library.dfy | 18 +++++++ docs/sbft-formal-model/proof.dfy | 59 +++++++++++++--------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/docs/sbft-formal-model/library/Library.dfy b/docs/sbft-formal-model/library/Library.dfy index 87b2f8e599..7a0c23dccd 100644 --- a/docs/sbft-formal-model/library/Library.dfy +++ b/docs/sbft-formal-model/library/Library.dfy @@ -1,4 +1,22 @@ module Library { + lemma MappedSetCardinality(p:set, f:(T)-->V, filtered:set) + requires forall t | t in p :: f.requires(t) + requires forall x, y | && x in p + && y in p + && f(x) == f(y) + :: x == y + requires filtered == set t | t in p :: f(t) + ensures |filtered| == |p| + { + if |p| != 0 { + var t0 :| t0 in p; + var sub_p := p - {t0}; + MappedSetCardinality(sub_p, f, set t | t in sub_p :: f(t)); + assert p == sub_p + {t0}; // Trigger extentionallity + assert (set t | t in p :: f(t)) == (set t | t in sub_p :: f(t)) + {f(t0)}; // Trigger extentionallity + } + } + function DropLast(theSeq: seq) : seq requires 0 < |theSeq| { diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 26a2101aa0..8c242d7aad 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -657,6 +657,8 @@ module Proof { reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); reveal_TemporarilyDisableCheckpointing(); reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); + reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); + if (h_step.SendCommitStep?) { forall commitMsg | && commitMsg in v'.network.sentMsgs && commitMsg.payload.Commit? @@ -671,25 +673,11 @@ module Proof { { var h_c := c.hosts[commitMsg.sender].replicaConstants; var h_v' := v'.hosts[commitMsg.sender].replicaVariables; - if(commitMsg in v.network.sentMsgs) { - assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v); - assert |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum(); - } else { - assert Replica.QuorumOfPrepares(h_c, h_v', h_step.seqID); - assert step.id == commitMsg.sender; - CountPrepareMessagesInWorkingWindow(c, v, commitMsg.sender, h_step.seqID); - CountPrepareMessages2(h_c, h_v, h_step.seqID); - assert |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v', commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum(); + if(commitMsg !in v.network.sentMsgs) { + // CountPrepareMessagesInWorkingWindow(c, v, commitMsg.sender, h_step.seqID); + CountPrepareMessages2(c, v, commitMsg.sender, h_c, h_v, h_step.seqID); } } - - - - - assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); - } else if (h_step.AdvanceWorkingWindowStep?) { - assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); - } else if(h_step.PerformStateTransferStep?) { assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); } else if(h_step.RecvPrepareStep?) { var h_c := c.hosts[step.id].replicaConstants; @@ -710,16 +698,15 @@ module Proof { var committer_v := v.hosts[commitMsg.sender].replicaVariables; var committer_v' := v'.hosts[commitMsg.sender].replicaVariables; if(commitMsg.sender == step.id) { - CountPrepareMessages2(committer_c, committer_v, seqID); + forall p | p in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID) + ensures p in Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID) { + assert p.sender in committer_v'.workingWindow.preparesRcvd[seqID]; // Trigger + } Library.SubsetCardinality(Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, seqID), Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v', seqID)); - } else { - assert committer_v == committer_v'; } } assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); - } else if(h_step.LeaveViewStep?) { - assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); } else { assert EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v'); } @@ -785,10 +772,34 @@ module Proof { // && v.hosts[replicaId].replicaVariables.workingWindow.preparesRcvd[seqID][sender].sender == sender; } - lemma CountPrepareMessages2(h_c:Replica.Constants, h_v:Replica.Variables, seqID:Messages.SequenceID) + lemma CountPrepareMessages2(c:Constants, v:Variables, replicaID:HostId, h_c:Replica.Constants, h_v:Replica.Variables, seqID:Messages.SequenceID) + requires v.WF(c) + requires IsHonestReplica(c, replicaID) + requires h_c == c.hosts[replicaID].replicaConstants + requires h_v == v.hosts[replicaID].replicaVariables + requires seqID in h_v.workingWindow.getActiveSequenceIDs(h_c) + requires RecordedPreparesHaveValidSenderID(c, v) ensures |Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, seqID)| == |h_v.workingWindow.preparesRcvd[seqID]| { - assume false; + var senders := h_v.workingWindow.preparesRcvd[seqID].Keys; + var f := sender requires sender in senders => h_v.workingWindow.preparesRcvd[seqID][sender]; + forall x, y | && x in senders + && y in senders + && f(x) == f(y) + ensures x == y { + reveal_RecordedPreparesHaveValidSenderID(); + assert c.clusterConfig.IsReplica(x); // Trigger + assert c.clusterConfig.IsReplica(y); // Trigger + } + var filtered := set x | x in senders :: f(x); + var filtered2 := set x | x in senders :: f(x); + assert filtered == filtered2; + + MappedSetCardinality(senders, f, filtered); + assert |senders| == |filtered|; + assert h_v.workingWindow.preparesRcvd[seqID].Keys == senders; + assert |h_v.workingWindow.preparesRcvd[seqID]| == |h_v.workingWindow.preparesRcvd[seqID].Keys|; + assert Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, seqID) == filtered; } lemma HonestPreservesEveryPrepareClientOpMatchesRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) From 3cd7d9f848fbc207be9f25c29f204dd1a239ada4 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 29 Mar 2023 22:39:34 +0300 Subject: [PATCH 26/38] Code cleanup. --- docs/sbft-formal-model/proof.dfy | 46 -------------------------------- 1 file changed, 46 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 8c242d7aad..79cf934d95 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -674,7 +674,6 @@ module Proof { var h_c := c.hosts[commitMsg.sender].replicaConstants; var h_v' := v'.hosts[commitMsg.sender].replicaVariables; if(commitMsg !in v.network.sentMsgs) { - // CountPrepareMessagesInWorkingWindow(c, v, commitMsg.sender, h_step.seqID); CountPrepareMessages2(c, v, commitMsg.sender, h_c, h_v, h_step.seqID); } } @@ -1590,21 +1589,6 @@ module Proof { reveal_RecordedViewChangeMsgsCameFromNetwork(); } - // This is part of a contradiction proof - lemma DoubleAgentDidNotCommit(c: Constants, - v:Variables, - doubleAgent:HostId, - seqID:Messages.SequenceID, - priorView:nat, - priorOperationWrapper:Messages.OperationWrapper, - vcMsg:Message) - requires Inv(c, v) - requires c.clusterConfig.IsHonestReplica(doubleAgent) - ensures !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) - { - assume false; - } - lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1870,7 +1854,6 @@ module Proof { assert certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper; } else { reveal_EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(); - assume EveryCommitMsgIsSupportedByAQuorumOfRecordedPrepares(c, v); assert false; // Couldn't have sent a Commit } } else { @@ -1901,7 +1884,6 @@ module Proof { assert certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper; } - //assume false; } else { assert oldCertificate.validFull(c.clusterConfig, seqID); assert oldCertificate.votes <= certificate.votes; @@ -2002,34 +1984,6 @@ module Proof { Replica.IsRecordedViewChangeMsgForView(h_c, h_v.viewChangeMsgsRecvd, h_v.view, vcMsg); } } - - //assume false; - // reveal_RecordedNewViewMsgsAreValid(); - // reveal_RecordedPreparesHaveValidSenderID(); - // reveal_RecordedPrePreparesRecvdCameFromNetwork(); - // reveal_RecordedPreparesRecvdCameFromNetwork(); - // reveal_RecordedPrePreparesMatchHostView(); - // reveal_RecordedPreparesMatchHostView(); - // reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); - // reveal_RecordedPreparesClientOpsMatchPrePrepare(); - // reveal_RecordedCommitsClientOpsMatchPrePrepare(); - // reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); - // reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - // reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); - // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); - // reveal_HonestReplicasLockOnPrepareForGivenView(); - // reveal_HonestReplicasLockOnCommitForGivenView(); - // reveal_CommitMsgsFromHonestSendersAgree(); - // reveal_RecordedCheckpointsRecvdCameFromNetwork(); - // reveal_UnCommitableAgreesWithPrepare(); - // reveal_UnCommitableAgreesWithRecordedPrePrepare(); - // reveal_HonestReplicasLeaveViewsBehind(); - // reveal_RecordedNewViewMsgsContainSentVCMsgs(); - // reveal_RecordedViewChangeMsgsCameFromNetwork(); - // reveal_SentViewChangesMsgsComportWithSentCommits(); - // reveal_EveryCommitMsgIsRememberedByItsSender(); - // reveal_RecordedViewChangeMsgsAreValid(); - // reveal_TemporarilyDisableCheckpointing(); } lemma HonestPreservesRecordedViewChangeMsgsAreValid(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) From 30ea07c973721d9667f3337e29631de96bf28590 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 30 Mar 2023 21:06:12 +0300 Subject: [PATCH 27/38] Progress on prooving HonestPreservesViewChangeMsgsFromHonestInNetworkAreValid. --- docs/sbft-formal-model/proof.dfy | 93 +++++++++++++++++++++++----- docs/sbft-formal-model/replica.i.dfy | 11 +++- 2 files changed, 88 insertions(+), 16 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 79cf934d95..8ee8e17a46 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -263,7 +263,7 @@ module Proof { && commitMsg.payload.operationWrapper == recordedPrePrepare.value.payload.operationWrapper) } - predicate {:opaque} EveryPrepareClientOpMatchesRecordedPrePrepare(c:Constants, v:Variables) { + predicate {:opaque} EveryPrepareMatchesRecordedPrePrepare(c:Constants, v:Variables) { && v.WF(c) && (forall prepareMsg | && prepareMsg in v.network.sentMsgs @@ -272,11 +272,12 @@ module Proof { && var replicaVariables := v.hosts[prepareMsg.sender].replicaVariables; && var replicaConstants := c.hosts[prepareMsg.sender].replicaConstants; && prepareMsg.payload.seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) - && prepareMsg.payload.view == replicaVariables.view :: && var recordedPrePrepare := v.hosts[prepareMsg.sender].replicaVariables.workingWindow.prePreparesRcvd[prepareMsg.payload.seqID]; + && var replicaVariables := v.hosts[prepareMsg.sender].replicaVariables; && recordedPrePrepare.Some? - && prepareMsg.payload.operationWrapper == recordedPrePrepare.value.payload.operationWrapper) + && prepareMsg.payload.operationWrapper == recordedPrePrepare.value.payload.operationWrapper + && prepareMsg.payload.view == replicaVariables.view) } predicate {:opaque} RecordedPreparesMatchHostView(c:Constants, v:Variables) { @@ -357,7 +358,7 @@ module Proof { && RecordedCommitsClientOpsMatchPrePrepare(c, v) && EverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v) && EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(c, v) - && EveryPrepareClientOpMatchesRecordedPrePrepare(c, v) + && EveryPrepareMatchesRecordedPrePrepare(c, v) && EveryCommitClientOpMatchesRecordedPrePrepare(c, v) && HonestReplicasLockOnPrepareForGivenView(c, v) && HonestReplicasLockOnCommitForGivenView(c, v) @@ -801,13 +802,13 @@ module Proof { assert Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, seqID) == filtered; } - lemma HonestPreservesEveryPrepareClientOpMatchesRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + lemma HonestPreservesEveryPrepareMatchesRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) - ensures EveryPrepareClientOpMatchesRecordedPrePrepare(c, v') + ensures EveryPrepareMatchesRecordedPrePrepare(c, v') { - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryPrepareMatchesRecordedPrePrepare(); reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); h_v.workingWindow.reveal_Shift(); @@ -846,7 +847,7 @@ module Proof { ensures HonestReplicasLockOnPrepareForGivenView(c, v') { reveal_HonestReplicasLockOnPrepareForGivenView(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryPrepareMatchesRecordedPrePrepare(); } lemma HonestPreservesHonestReplicasLockOnCommitForGivenView(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) @@ -1034,7 +1035,7 @@ module Proof { // reveal_RecordedCommitsClientOpsMatchPrePrepare(); // reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); // reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - // reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + // reveal_EveryPrepareMatchesRecordedPrePrepare(); // reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); // reveal_HonestReplicasLockOnPrepareForGivenView(); // reveal_HonestReplicasLockOnCommitForGivenView(); @@ -1695,7 +1696,7 @@ module Proof { reveal_RecordedCommitsClientOpsMatchPrePrepare(); reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryPrepareMatchesRecordedPrePrepare(); reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); reveal_HonestReplicasLockOnPrepareForGivenView(); reveal_HonestReplicasLockOnCommitForGivenView(); @@ -1712,7 +1713,69 @@ module Proof { ensures ViewChangeMsgsFromHonestInNetworkAreValid(c, v') { reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); - assume false; + forall viewChangeMsg | + && viewChangeMsg in v'.network.sentMsgs + && viewChangeMsg.payload.ViewChangeMsg? + && var replicaIdx := viewChangeMsg.sender; + && IsHonestReplica(c, replicaIdx) + ensures && var replicaIdx := viewChangeMsg.sender; + && viewChangeMsg.payload.checked(c.clusterConfig, v'.network.sentMsgs) { + var h_c := c.hosts[step.id].replicaConstants; + var replicaIdx := viewChangeMsg.sender; + if viewChangeMsg !in v.network.sentMsgs { + assert h_step.LeaveViewStep? by { + reveal_RecordedViewChangeMsgsCameFromNetwork(); + } + reveal_EveryPrepareMatchesRecordedPrePrepare(); + reveal_TemporarilyDisableCheckpointing(); + var certificates := viewChangeMsg.payload.certificates; + forall seqID | seqID in certificates + ensures && certificates[seqID].valid(c.clusterConfig, seqID) + && certificates[seqID].votesRespectView(viewChangeMsg.payload.newView) + && certificates[seqID].votes <= v.network.sentMsgs { + var certificate := certificates[seqID]; + var workingWindowPreparesRecvd := Replica.ExtractPreparesFromWorkingWindow(h_c, h_v, seqID); + var viewChangeMsgPreparesRecvd := Replica.ExtractPreparesFromLatestViewChangeMsg(h_c, h_v, seqID); + if |workingWindowPreparesRecvd| >= h_c.clusterConfig.AgreementQuorum() + { + reveal_RecordedPreparesRecvdCameFromNetwork(); + assert certificates == Replica.ExtractCertificates(h_c, h_v); + assert certificate == Replica.ExtractCertificateForSeqID(h_c, h_v, seqID); + if !certificate.empty() { + var key :| && key in h_v.workingWindow.preparesRcvd[seqID] + && h_v.workingWindow.preparesRcvd[seqID][key].payload == certificate.prototype(); + + assert certificate.validFull(c.clusterConfig, seqID); + } + assert certificate.valid(c.clusterConfig, seqID); + assert certificate.votesRespectView(viewChangeMsg.payload.newView); + } + else if |viewChangeMsgPreparesRecvd| >= c.clusterConfig.AgreementQuorum() { + assume false; + assert certificate.valid(c.clusterConfig, seqID); + assert certificate.votesRespectView(viewChangeMsg.payload.newView); + assert certificate.votes <= v.network.sentMsgs; + } + else { + assert certificate.valid(c.clusterConfig, seqID); + assert certificate.votesRespectView(viewChangeMsg.payload.newView); + assert certificate.votes <= v.network.sentMsgs; + } + } + assert Replica.ExtractValidStableCheckpointProof(h_c, h_v).msgs == {} by { + // We need 2 Invariants: + // 1. All Checkpoint messages we record are in the netork. + // 2. Checkpint messages from honest senders precede its last stable variable. + // Since Checkpointing is disabled the honest senders have last stable == 0, + // therefore we cannot get a quorum of honest senders. + assume false; + } + assert h_v.workingWindow.lastStableCheckpoint == 0; + assert viewChangeMsg.payload.checked(c.clusterConfig, v'.network.sentMsgs); + } else { + assert viewChangeMsg.payload.checked(c.clusterConfig, v'.network.sentMsgs); + } + } } lemma HonestPreservesEveryCommitMsgIsRememberedByItsSenderForCommitStep( @@ -2040,7 +2103,7 @@ module Proof { HonestPreservesRecordedCommitsClientOpsMatchPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesEverySentIntraViewMsgIsInWorkingWindowOrBefore(c, v, v', step, h_v, h_step); HonestPreservesEverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(c, v, v', step, h_v, h_step); - HonestPreservesEveryPrepareClientOpMatchesRecordedPrePrepare(c, v, v', step, h_v, h_step); + HonestPreservesEveryPrepareMatchesRecordedPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesEveryCommitClientOpMatchesRecordedPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesHonestReplicasLockOnPrepareForGivenView(c, v, v', step, h_v, h_step); HonestPreservesHonestReplicasLockOnCommitForGivenView(c, v, v', step, h_v, h_step); @@ -2097,8 +2160,8 @@ module Proof { assert EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(c, v') by { reveal_EverySentIntraViewMsgIsForAViewLessOrEqualToSenderView(); } - assert EveryPrepareClientOpMatchesRecordedPrePrepare(c, v') by { - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + assert EveryPrepareMatchesRecordedPrePrepare(c, v') by { + reveal_EveryPrepareMatchesRecordedPrePrepare(); } assert EveryCommitClientOpMatchesRecordedPrePrepare(c, v') by { reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); @@ -2167,7 +2230,7 @@ module Proof { assert Inv(c, v) by { reveal_RecordedNewViewMsgsAreValid(); reveal_RecordedCommitsClientOpsMatchPrePrepare(); - reveal_EveryPrepareClientOpMatchesRecordedPrePrepare(); + reveal_EveryPrepareMatchesRecordedPrePrepare(); reveal_EveryCommitClientOpMatchesRecordedPrePrepare(); reveal_HonestReplicasLockOnCommitForGivenView(); reveal_CommitMsgsFromHonestSendersAgree(); diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 9b4851b066..a2cd75d547 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -514,7 +514,7 @@ module Replica { ViewChangeMsg( newView, v.workingWindow.lastStableCheckpoint, - CheckpointsQuorum(ExtractStableCheckpointProof(c, v)), + ExtractValidStableCheckpointProof(c, v), ExtractCertificates(c, v))); // TODO: this should follow from the invariant and from the way we collect prepares. // && (forall seqID :: seqID in vcMsg.payload.certificates ==> @@ -525,6 +525,15 @@ module Replica { && msgOps.send.value == vcMsg } + function ExtractValidStableCheckpointProof(c:Constants, v:Variables) : CheckpointsQuorum + requires v.WF(c) + { + var proof := ExtractStableCheckpointProof(c, v); + if |proof| >= c.clusterConfig.AgreementQuorum() + then CheckpointsQuorum(proof) + else CheckpointsQuorum({}) + } + function ExtractStableCheckpointProof(c:Constants, v:Variables) : set> requires v.WF(c) { From 0d17ebe3af2b0b2e652ce6cde80147ec3abee5a1 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 30 Mar 2023 22:15:26 +0300 Subject: [PATCH 28/38] Progress on HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare. --- docs/sbft-formal-model/proof.dfy | 56 +++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 8ee8e17a46..57303ccd93 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -97,6 +97,14 @@ module Proof { && |Replica.ExtractPreparesFromWorkingWindow(committer_c, committer_v, commitMsg.payload.seqID)| >= c.clusterConfig.AgreementQuorum()) } + predicate CertificateComportsWithCommit(c:Constants, certificate:Messages.PreparedCertificate, commitMsg:Message) { + && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) + && !certificate.empty() + && certificate.prototype().view >= commitMsg.payload.view + && (certificate.prototype().view == commitMsg.payload.view + ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + } + predicate {:opaque} EveryCommitMsgIsRememberedByItsSender(c:Constants, v:Variables) { //TODO: this does not cover Checkpointing && v.WF(c) && (forall commitMsg | && commitMsg in v.network.sentMsgs @@ -108,11 +116,23 @@ module Proof { :: && var h_c := c.hosts[commitMsg.sender].replicaConstants; && var h_v := v.hosts[commitMsg.sender].replicaVariables; && var certificate := Replica.ExtractCertificateForSeqID(h_c, h_v, commitMsg.payload.seqID); - && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) - && !certificate.empty() - && certificate.prototype().view >= commitMsg.payload.view - && (certificate.prototype().view == commitMsg.payload.view - ==> certificate.prototype().operationWrapper == commitMsg.payload.operationWrapper) + && CertificateComportsWithCommit(c, certificate, commitMsg) + ) + } + + predicate {:opaque} EveryCommitMsgIsRememberedByVCMsgs(c:Constants, v:Variables) { + && v.WF(c) + && (forall commitMsg, viewChangeMsg | + && commitMsg in v.network.sentMsgs + && viewChangeMsg in v.network.sentMsgs + && commitMsg.payload.Commit? + && viewChangeMsg.payload.ViewChangeMsg? + && viewChangeMsg.sender == commitMsg.sender + && IsHonestReplica(c, commitMsg.sender) + && commitMsg.payload.seqID > viewChangeMsg.payload.lastStableCheckpoint + && commitMsg.payload.view < viewChangeMsg.payload.newView + :: && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; + && CertificateComportsWithCommit(c, certificate, commitMsg) ) } @@ -1636,15 +1656,27 @@ module Proof { assert vcMsg in v.network.sentMsgs by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); } - assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - // Need to finish this proof + var certView := vcMsg.payload.certificates[seqID].prototype().view; + if certView <= priorView { + assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + // viewchangemessages from honest sender comport with uncommitable in view + //reveal_SentViewChangesMsgsComportWithUncommitableInView(); + //assume false; + //reveal_CommitMsgsFromHonestSendersAgree(); + reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assume EveryCommitMsgIsRememberedByVCMsgs(c, v); + } + } else { // certView > priorView + /* + Grab the cert + One of the prepares in the cert has to be from an honest node + This prepare has to be in the network + Apply UnCommitableAgreesWithPrepare + Hint: We don't need a double agent in this case. We only need an honest certificate sender. + */ assume false; - // viewchangemessages from honest sender comport with uncommitable in view - reveal_SentViewChangesMsgsComportWithUncommitableInView(); - assume SentViewChangesMsgsComportWithUncommitableInView(c, v); - assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); - // Left off here. } + assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { reveal_HonestReplicasLeaveViewsBehind(); assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; From 1ad07f8b8e4505a7dc7bbfa246848b5e096e61c0 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 3 Apr 2023 22:12:04 +0300 Subject: [PATCH 29/38] Progress on proof for HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare. Minor cleanup: GetMsgFromHonestSender does not need Distributed system's variables. --- docs/sbft-formal-model/proof.dfy | 66 ++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 57303ccd93..b1b2d21efb 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1357,7 +1357,7 @@ module Proof { { reveal_EveryCommitMsgIsSupportedByAQuorumOfSentPrepares(); var prepares := sentPreparesForSeqID(c, v, commitMsg.payload.view, commitMsg.payload.seqID, commitMsg.payload.operationWrapper); - prepare := GetMsgFromHonestSender(c, v, prepares); + prepare := GetMsgFromHonestSender(c, prepares); } function FaultyReplicas(c: Constants) : (faulty:set) @@ -1408,9 +1408,9 @@ module Proof { honest := result; } - lemma GetMsgFromHonestSender(c:Constants, v:Variables, quorum:set) + lemma GetMsgFromHonestSender(c:Constants, quorum:set) returns (message:Message) - requires Inv(c, v) + requires c.WF() requires |Messages.sendersOf(quorum)| >= c.clusterConfig.AgreementQuorum() requires Messages.sendersOf(quorum) <= getAllReplicas(c) ensures c.clusterConfig.IsHonestReplica(message.sender) @@ -1488,7 +1488,7 @@ module Proof { reveal_UnCommitableAgreesWithPrepare(); if operationWrapper1 != operationWrapper2 { UniqueSendersCardinality(commits2); - var commit := GetMsgFromHonestSender(c, v, commits2); + var commit := GetMsgFromHonestSender(c, commits2); var prepare := GetPrepareFromHonestSenderForCommit(c, v, commit); // This term instantiates UnCommitableAgreesWithPrepare UniqueSendersCardinality(commits1); Library.SubsetCardinality(Messages.sendersOf(commits1), ReplicasThatCanCommitInView(c, v, seqID, view1, operationWrapper1)); @@ -1656,33 +1656,43 @@ module Proof { assert vcMsg in v.network.sentMsgs by { reveal_RecordedNewViewMsgsContainSentVCMsgs(); } - var certView := vcMsg.payload.certificates[seqID].prototype().view; - if certView <= priorView { - assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - // viewchangemessages from honest sender comport with uncommitable in view - //reveal_SentViewChangesMsgsComportWithUncommitableInView(); - //assume false; - //reveal_CommitMsgsFromHonestSendersAgree(); - reveal_EveryCommitMsgIsRememberedByVCMsgs(); - assume EveryCommitMsgIsRememberedByVCMsgs(c, v); - } - } else { // certView > priorView - /* - Grab the cert - One of the prepares in the cert has to be from an honest node - This prepare has to be in the network - Apply UnCommitableAgreesWithPrepare - Hint: We don't need a double agent in this case. We only need an honest certificate sender. - */ - assume false; - } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - reveal_HonestReplicasLeaveViewsBehind(); - assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + reveal_HonestReplicasLeaveViewsBehind(); + assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + } + + if (seqID !in vcMsg.payload.certificates) { + // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. + } else { + var certView := vcMsg.payload.certificates[seqID].prototype().view; + if certView <= priorView { + assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + // viewchangemessages from honest sender comport with uncommitable in view + //reveal_SentViewChangesMsgsComportWithUncommitableInView(); + //assume false; + //reveal_CommitMsgsFromHonestSendersAgree(); + reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assume EveryCommitMsgIsRememberedByVCMsgs(c, v); + } + assert doubleAgent !in troubleMakers; + assert false; + } else { // certView > priorView // Use the mutual induction hypothesis to get UncommitableInView directly. + /* + Grab the cert + One of the prepares in the cert has to be from an honest node + This prepare has to be in the network + Apply UnCommitableAgreesWithPrepare + Hint: We don't need a double agent in this case. We only need an honest certificate sender. + */ + var cert := vcMsg.payload.certificates[seqID]; + var prepareFromHonest := GetMsgFromHonestSender(c, cert.votes); + assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper) by { + reveal_UnCommitableAgreesWithPrepare(); + } + assert false; + } } - assert doubleAgent !in viewChangers; - assert false; } var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; assert prePrepareMsg.payload.seqID == seqID; From 80fefacf89f7e491306692378255874c96ee360a Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Mon, 10 Apr 2023 14:07:08 +0300 Subject: [PATCH 30/38] Hints added for next steps. --- docs/sbft-formal-model/proof.dfy | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index b1b2d21efb..f2c80fb769 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1617,7 +1617,7 @@ module Proof { ensures UnCommitableAgreesWithRecordedPrePrepare(c, v') { reveal_UnCommitableAgreesWithRecordedPrePrepare(); - reveal_RecordedNewViewMsgsAreValid(); + //reveal_RecordedNewViewMsgsAreValid(); Selectively reveal in assert by-s (causes triggering explosion) forall replicaIdx, seqID, priorView:nat, priorOperationWrapper:Messages.OperationWrapper | && IsHonestReplica(c, replicaIdx) && var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; @@ -1665,7 +1665,7 @@ module Proof { if (seqID !in vcMsg.payload.certificates) { // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. } else { - var certView := vcMsg.payload.certificates[seqID].prototype().view; + var certView := vcMsg.payload.certificates[seqID].prototype().view; // We need to differentiate the first hop of the VC!!! if certView <= priorView { assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // viewchangemessages from honest sender comport with uncommitable in view From 52b3a7510e8e503d6e1d2a1645f087ef20d39a14 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 12 Apr 2023 20:15:54 +0300 Subject: [PATCH 31/38] Minor fixes and hints for next steps. --- docs/sbft-formal-model/proof.dfy | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index f2c80fb769..c362ebb74b 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -98,6 +98,8 @@ module Proof { } predicate CertificateComportsWithCommit(c:Constants, certificate:Messages.PreparedCertificate, commitMsg:Message) { + && c.WF() + && commitMsg.payload.Commit? && certificate.valid(c.clusterConfig, commitMsg.payload.seqID) && !certificate.empty() && certificate.prototype().view >= commitMsg.payload.view @@ -131,7 +133,8 @@ module Proof { && IsHonestReplica(c, commitMsg.sender) && commitMsg.payload.seqID > viewChangeMsg.payload.lastStableCheckpoint && commitMsg.payload.view < viewChangeMsg.payload.newView - :: && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; + :: && commitMsg.payload.seqID in viewChangeMsg.payload.certificates + && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; && CertificateComportsWithCommit(c, certificate, commitMsg) ) } @@ -292,12 +295,12 @@ module Proof { && var replicaVariables := v.hosts[prepareMsg.sender].replicaVariables; && var replicaConstants := c.hosts[prepareMsg.sender].replicaConstants; && prepareMsg.payload.seqID in replicaVariables.workingWindow.getActiveSequenceIDs(replicaConstants) + && prepareMsg.payload.view == replicaVariables.view // There might be prepare messages for prior views that the sender no longer records. :: && var recordedPrePrepare := v.hosts[prepareMsg.sender].replicaVariables.workingWindow.prePreparesRcvd[prepareMsg.payload.seqID]; && var replicaVariables := v.hosts[prepareMsg.sender].replicaVariables; && recordedPrePrepare.Some? - && prepareMsg.payload.operationWrapper == recordedPrePrepare.value.payload.operationWrapper - && prepareMsg.payload.view == replicaVariables.view) + && prepareMsg.payload.operationWrapper == recordedPrePrepare.value.payload.operationWrapper) } predicate {:opaque} RecordedPreparesMatchHostView(c:Constants, v:Variables) { @@ -815,7 +818,7 @@ module Proof { var filtered2 := set x | x in senders :: f(x); assert filtered == filtered2; - MappedSetCardinality(senders, f, filtered); + MappedSetCardinality(senders, f, filtered); // This is a Dafny triggering problem. assert |senders| == |filtered|; assert h_v.workingWindow.preparesRcvd[seqID].Keys == senders; assert |h_v.workingWindow.preparesRcvd[seqID]| == |h_v.workingWindow.preparesRcvd[seqID].Keys|; @@ -826,7 +829,6 @@ module Proof { requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) ensures EveryPrepareMatchesRecordedPrePrepare(c, v') - { reveal_EveryPrepareMatchesRecordedPrePrepare(); reveal_EverySentIntraViewMsgIsInWorkingWindowOrBefore(); @@ -1663,6 +1665,7 @@ module Proof { } if (seqID !in vcMsg.payload.certificates) { + assume false; // Need help from Oded in this branch. // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. } else { var certView := vcMsg.payload.certificates[seqID].prototype().view; // We need to differentiate the first hop of the VC!!! From a3fba37dbfed698330950edfee21abd444af8435 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Tue, 25 Apr 2023 10:57:20 +0300 Subject: [PATCH 32/38] Refactor RecordedNewViewMsgsContainSentVCMsgs. RecordedNewViewMsgsContainSentVCMsgs changed to RecordedNewViewMsgsAreChecked. In this new predicate we check whether the contents of each recorded new view msg are correct according to the protocol (the VC msgs contained are valid and from different senders, etc). Temporary workaround in Library's MappedSetCardinality added to address Dafny's triggering problem. --- docs/sbft-formal-model/library/Library.dfy | 5 +- docs/sbft-formal-model/proof.dfy | 59 ++++++++++++++++------ 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/docs/sbft-formal-model/library/Library.dfy b/docs/sbft-formal-model/library/Library.dfy index 7a0c23dccd..d23d1d9ad4 100644 --- a/docs/sbft-formal-model/library/Library.dfy +++ b/docs/sbft-formal-model/library/Library.dfy @@ -5,9 +5,12 @@ module Library { && y in p && f(x) == f(y) :: x == y - requires filtered == set t | t in p :: f(t) + //requires filtered == set t | t in p :: f(t) // TODO: bug in Dafny makes this un-triggerable. Uncomment when we find a workaround! ensures |filtered| == |p| { + assert filtered == set t | t in p :: f(t) by { + assume false; // TODO: temporary fix for triggering problem in Dafny. + } if |p| != 0 { var t0 :| t0 in p; var sub_p := p - {t0}; diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index c362ebb74b..f5ec10402f 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -390,7 +390,7 @@ module Proof { && UnCommitableAgreesWithPrepare(c, v) && UnCommitableAgreesWithRecordedPrePrepare(c, v)//Unfinished proof. && HonestReplicasLeaveViewsBehind(c, v) - && RecordedNewViewMsgsContainSentVCMsgs(c, v) + && RecordedNewViewMsgsAreChecked(c, v) && RecordedViewChangeMsgsCameFromNetwork(c, v) && OneViewChangeMessageFromReplicaPerView(c, v) && SentViewChangesMsgsComportWithSentCommits(c, v)//Unfinished proof. This predicate is false. @@ -1248,13 +1248,14 @@ module Proof { && (forall msg | msg in msgs :: c.clusterConfig.IsReplica(msg.sender)) } - predicate {:opaque} RecordedNewViewMsgsContainSentVCMsgs(c:Constants, v:Variables) { + predicate {:opaque} RecordedNewViewMsgsAreChecked(c:Constants, v:Variables) { && v.WF(c) && (forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs :: && newViewMsg.payload.vcMsgs.msgs <= v.network.sentMsgs - && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs)) + && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs) + && newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs)) } predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { @@ -1560,12 +1561,12 @@ module Proof { reveal_HonestReplicasLeaveViewsBehind(); } - lemma HonestPreservesRecordedNewViewMsgsContainSentVCMsgs(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) + lemma HonestPreservesRecordedNewViewMsgsAreChecked(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) - ensures RecordedNewViewMsgsContainSentVCMsgs(c, v') + ensures RecordedNewViewMsgsAreChecked(c, v') { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedNewViewMsgsAreChecked(); reveal_RecordedViewChangeMsgsCameFromNetwork(); forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) @@ -1645,9 +1646,12 @@ module Proof { var troubleMakers := ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper); if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { // Contradiction hypothesis + assert newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs) by { + reveal_RecordedNewViewMsgsAreChecked(); + } UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); assert viewChangers <= getAllReplicas(c) by { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedNewViewMsgsAreChecked(); } var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); assert !c.clusterConfig.IsFaultyReplica(doubleAgent); @@ -1656,7 +1660,7 @@ module Proof { && vcMsg.sender == doubleAgent; assert vcMsg.payload.ViewChangeMsg?; assert vcMsg in v.network.sentMsgs by { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedNewViewMsgsAreChecked(); } assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { @@ -1665,21 +1669,44 @@ module Proof { } if (seqID !in vcMsg.payload.certificates) { - assume false; // Need help from Oded in this branch. + reveal_TemporarilyDisableCheckpointing(); + assume false; + } else if (vcMsg.payload.certificates[seqID].empty()) { + // Need help from Oded in this branch. // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. + assume false; } else { - var certView := vcMsg.payload.certificates[seqID].prototype().view; // We need to differentiate the first hop of the VC!!! - if certView <= priorView { + var certificate := vcMsg.payload.certificates[seqID]; + var certView := certificate.prototype().view; // We need to differentiate the first hop of the VC!!! + if certView < priorView { assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { // viewchangemessages from honest sender comport with uncommitable in view //reveal_SentViewChangesMsgsComportWithUncommitableInView(); //assume false; //reveal_CommitMsgsFromHonestSendersAgree(); reveal_EveryCommitMsgIsRememberedByVCMsgs(); - assume EveryCommitMsgIsRememberedByVCMsgs(c, v); + assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. } assert doubleAgent !in troubleMakers; assert false; + } else if certView == priorView { + // Interesting branch - if there was a commit its operation wraper has to match the prepared certificate. + if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { + // operation wrapers agree + var sentCommit := Network.Message(doubleAgent, + Messages.Commit(priorView, + seqID, + priorOperationWrapper)); + assert sentCommit in v.network.sentMsgs; + assert CertificateComportsWithCommit(c, certificate, sentCommit) by { + reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. + } + assert sentCommit.payload.operationWrapper == certificate.prototype().operationWrapper; + } else { + assert doubleAgent !in troubleMakers; + assert false; + } } else { // certView > priorView // Use the mutual induction hypothesis to get UncommitableInView directly. /* Grab the cert @@ -2158,7 +2185,7 @@ module Proof { HonestPreservesUnCommitableAgreesWithRecordedPrePrepare(c, v, v', step, h_v, h_step); HonestPreservesUnCommitableAgreesWithPrepare(c, v, v', step, h_v, h_step); HonestPreservesHonestReplicasLeaveViewsBehind(c, v, v', step, h_v, h_step); - HonestPreservesRecordedNewViewMsgsContainSentVCMsgs(c, v, v', step, h_v, h_step); + HonestPreservesRecordedNewViewMsgsAreChecked(c, v, v', step, h_v, h_step); HonestPreservesRecordedViewChangeMsgsCameFromNetwork(c, v, v', step, h_v, h_step); HonestPreservesSentViewChangesMsgsComportWithSentCommits(c, v, v', step, h_v, h_step); HonestPreservesEveryCommitMsgIsRememberedByItsSender(c, v, v', step, h_v, h_step); @@ -2244,8 +2271,8 @@ module Proof { assert HonestReplicasLeaveViewsBehind(c, v') by { reveal_HonestReplicasLeaveViewsBehind(); } - assert RecordedNewViewMsgsContainSentVCMsgs(c, v') by { - reveal_RecordedNewViewMsgsContainSentVCMsgs(); + assert RecordedNewViewMsgsAreChecked(c, v') by { + reveal_RecordedNewViewMsgsAreChecked(); } assert RecordedViewChangeMsgsCameFromNetwork(c, v') by { reveal_RecordedViewChangeMsgsCameFromNetwork(); @@ -2294,7 +2321,7 @@ module Proof { reveal_UnCommitableAgreesWithPrepare(); reveal_UnCommitableAgreesWithRecordedPrePrepare(); reveal_HonestReplicasLeaveViewsBehind(); - reveal_RecordedNewViewMsgsContainSentVCMsgs(); + reveal_RecordedNewViewMsgsAreChecked(); reveal_RecordedViewChangeMsgsCameFromNetwork(); reveal_SentViewChangesMsgsComportWithSentCommits(); reveal_EveryCommitMsgIsRememberedByItsSender(); From 59867cb7c3a8a824444777747a28158ebedc2961 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Tue, 25 Apr 2023 23:36:45 +0300 Subject: [PATCH 33/38] Refactor HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare. We split the reasoning to first prove correctness wen moving to consecutive view. --- docs/sbft-formal-model/proof.dfy | 192 ++++++++++++++++++------------- 1 file changed, 112 insertions(+), 80 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index f5ec10402f..59181a50b1 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1613,6 +1613,23 @@ module Proof { reveal_RecordedViewChangeMsgsCameFromNetwork(); } + lemma InvokeEveryCommitMsgIsRememberedByVCMsgs(c: Constants, v:Variables, commitMsg:Message, viewChangeMsg:Message) + requires EveryCommitMsgIsRememberedByVCMsgs(c, v) + requires commitMsg in v.network.sentMsgs + requires viewChangeMsg in v.network.sentMsgs + requires commitMsg.payload.Commit? + requires viewChangeMsg.payload.ViewChangeMsg? + requires viewChangeMsg.sender == commitMsg.sender + requires IsHonestReplica(c, commitMsg.sender) + requires commitMsg.payload.seqID > viewChangeMsg.payload.lastStableCheckpoint + requires commitMsg.payload.view < viewChangeMsg.payload.newView + ensures && commitMsg.payload.seqID in viewChangeMsg.payload.certificates + && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; + && CertificateComportsWithCommit(c, certificate, commitMsg) + { + reveal_EveryCommitMsgIsRememberedByVCMsgs(); + } + lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1637,97 +1654,112 @@ module Proof { if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { // Interesting case is when we record a new PrePrepare. var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs // The checks that the Replica does before recording && msg.payload.newView == h_v.view; // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare - if |newViewMsgs| == 0 { - assert h_v.view == 0; - assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. - } - var newViewMsg :| newViewMsg in newViewMsgs; - var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); - var troubleMakers := ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper); - if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { - // Contradiction hypothesis - assert newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs) by { - reveal_RecordedNewViewMsgsAreChecked(); - } - UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); - assert viewChangers <= getAllReplicas(c) by { - reveal_RecordedNewViewMsgsAreChecked(); - } - var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); - assert !c.clusterConfig.IsFaultyReplica(doubleAgent); - assert doubleAgent in viewChangers; - var vcMsg:Message :| && vcMsg in newViewMsg.payload.vcMsgs.msgs - && vcMsg.sender == doubleAgent; - assert vcMsg.payload.ViewChangeMsg?; - assert vcMsg in v.network.sentMsgs by { - reveal_RecordedNewViewMsgsAreChecked(); - } - assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - reveal_HonestReplicasLeaveViewsBehind(); - assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + var newView := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value.payload.view; + if priorView + 1 == newView { + if |newViewMsgs| == 0 { + assert h_v.view == 0; + assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. } + var newViewMsg :| newViewMsg in newViewMsgs; + var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); + var troubleMakers := ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper); + if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { + // Contradiction hypothesis + assert newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs) by { + reveal_RecordedNewViewMsgsAreChecked(); + } + UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); + assert viewChangers <= getAllReplicas(c) by { + reveal_RecordedNewViewMsgsAreChecked(); + } + var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); + assert !c.clusterConfig.IsFaultyReplica(doubleAgent); + assert doubleAgent in viewChangers; + var vcMsg:Message :| && vcMsg in newViewMsg.payload.vcMsgs.msgs + && vcMsg.sender == doubleAgent; + assert vcMsg.payload.ViewChangeMsg?; + assert vcMsg in v.network.sentMsgs by { + reveal_RecordedNewViewMsgsAreChecked(); + } - if (seqID !in vcMsg.payload.certificates) { - reveal_TemporarilyDisableCheckpointing(); - assume false; - } else if (vcMsg.payload.certificates[seqID].empty()) { - // Need help from Oded in this branch. - // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. - assume false; - } else { - var certificate := vcMsg.payload.certificates[seqID]; - var certView := certificate.prototype().view; // We need to differentiate the first hop of the VC!!! - if certView < priorView { - assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - // viewchangemessages from honest sender comport with uncommitable in view - //reveal_SentViewChangesMsgsComportWithUncommitableInView(); - //assume false; - //reveal_CommitMsgsFromHonestSendersAgree(); - reveal_EveryCommitMsgIsRememberedByVCMsgs(); - assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. - } - assert doubleAgent !in troubleMakers; - assert false; - } else if certView == priorView { - // Interesting branch - if there was a commit its operation wraper has to match the prepared certificate. - if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { - // operation wrapers agree - var sentCommit := Network.Message(doubleAgent, - Messages.Commit(priorView, - seqID, - priorOperationWrapper)); - assert sentCommit in v.network.sentMsgs; - assert CertificateComportsWithCommit(c, certificate, sentCommit) by { - reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + reveal_HonestReplicasLeaveViewsBehind(); + assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + } + + if (seqID !in vcMsg.payload.certificates) { + reveal_TemporarilyDisableCheckpointing(); + assume false; + } else if (vcMsg.payload.certificates[seqID].empty()) { + // Need help from Oded in this branch. + // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. + assume false; + } else { + var certificate := vcMsg.payload.certificates[seqID]; + var certView := certificate.prototype().view; // We need to differentiate the first hop of the VC!!! + var sentCommit := Network.Message(doubleAgent, + Messages.Commit(priorView, + seqID, + priorOperationWrapper)); + if certView < priorView { + assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { // Contradiction + // viewchangemessages from honest sender comport with uncommitable in view + //reveal_SentViewChangesMsgsComportWithUncommitableInView(); + //assume false; + //reveal_CommitMsgsFromHonestSendersAgree(); + assert vcMsg in v.network.sentMsgs; + var doubleAgentCommitMsg := Network.Message(doubleAgent, + Messages.Commit(priorView, + seqID, + priorOperationWrapper)); + assert doubleAgentCommitMsg in v.network.sentMsgs; + //reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. + //assert CertificateComportsWithCommit(c, certificate, sentCommit); + reveal_TemporarilyDisableCheckpointing(); + InvokeEveryCommitMsgIsRememberedByVCMsgs(c, v, sentCommit, vcMsg); + assert false; + } + } + assert doubleAgent !in troubleMakers; + assert false; + } else if certView == priorView { + // Interesting branch - if there was a commit its operation wraper has to match the prepared certificate. + if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { + // operation wrapers agree + assert sentCommit in v.network.sentMsgs; assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. + reveal_TemporarilyDisableCheckpointing(); + InvokeEveryCommitMsgIsRememberedByVCMsgs(c, v, sentCommit, vcMsg); + assert sentCommit.payload.operationWrapper == certificate.prototype().operationWrapper; + // Left off here, every other certificate has to be for the same veiw, + // therefore whichever certificate the new view chose, it must be from + // the same view as the double agent certificate. The certs have to + // agree on the operation wrapper by prepare quorum intersection. + assert false; + } else { + assert doubleAgent !in troubleMakers; + assert false; } - assert sentCommit.payload.operationWrapper == certificate.prototype().operationWrapper; } else { - assert doubleAgent !in troubleMakers; + // A valid ViewChange msg cannot present a certificate for a view past the one we are ViewChangig assert false; } - } else { // certView > priorView // Use the mutual induction hypothesis to get UncommitableInView directly. - /* - Grab the cert - One of the prepares in the cert has to be from an honest node - This prepare has to be in the network - Apply UnCommitableAgreesWithPrepare - Hint: We don't need a double agent in this case. We only need an honest certificate sender. - */ - var cert := vcMsg.payload.certificates[seqID]; - var prepareFromHonest := GetMsgFromHonestSender(c, cert.votes); - assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper) by { - reveal_UnCommitableAgreesWithPrepare(); - } - assert false; } + assert false; // Contradction is shown } + var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + assert prePrepareMsg.payload.seqID == seqID; + assert |ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); + assert UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + } else { + // call back to inductive step + // certView > priorView // Use the mutual induction hypothesis to get UncommitableInView directly. + assume false; } - var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; - assert prePrepareMsg.payload.seqID == seqID; - assert |ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); - assert UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + } else { var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); From 0f0d9a945b406493945155186314c2736d8e9e6c Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 27 Apr 2023 11:23:03 +0300 Subject: [PATCH 34/38] Split the reasoning in HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare. We split the reasoning for proving HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare in 2 cases (separate lemmas) for the case we have no Prepared Certificate for the Sequence ID of the PrePrepare and a case that has a set of Prepared Certificates that have to be considered. In replica.i.dfy we create a function to extract the relevant Prepared Certificate from a New View message for a given Sequence ID. --- docs/sbft-formal-model/messages.dfy | 9 +- docs/sbft-formal-model/proof.dfy | 211 ++++++++++++--------------- docs/sbft-formal-model/replica.i.dfy | 26 ++-- 3 files changed, 115 insertions(+), 131 deletions(-) diff --git a/docs/sbft-formal-model/messages.dfy b/docs/sbft-formal-model/messages.dfy index b0d312b102..8672dc60ad 100644 --- a/docs/sbft-formal-model/messages.dfy +++ b/docs/sbft-formal-model/messages.dfy @@ -106,7 +106,7 @@ module Messages { :: m1.sender != m2.sender) } - // Define your Message datatype here. + // Define your Message datatype here. // TODO: rename to payload. datatype Message = | PrePrepare(view:ViewNum, seqID:SequenceID, operationWrapper:OperationWrapper) | Prepare(view:ViewNum, seqID:SequenceID, operationWrapper:OperationWrapper) | Commit(view:ViewNum, seqID:SequenceID, operationWrapper:OperationWrapper) @@ -170,5 +170,12 @@ module Messages { || Commit? } } + predicate ValidNewViewMsg(clusterConfig:ClusterConfig.Constants, msg:Network.Message) + { + && clusterConfig.WF() + && msg.payload.NewViewMsg? + && msg.payload.valid(clusterConfig) + && clusterConfig.PrimaryForView(msg.payload.newView) == msg.sender + } // ToDo: ClientReply } diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 59181a50b1..ed77d4f263 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1243,19 +1243,13 @@ module Proof { && replicaVariables.view >= viewChangeMsg.payload.newView) } - predicate MessagesAreFromReplicas(c:Constants, msgs:set) { - && c.WF() - && (forall msg | msg in msgs :: c.clusterConfig.IsReplica(msg.sender)) - } - predicate {:opaque} RecordedNewViewMsgsAreChecked(c:Constants, v:Variables) { && v.WF(c) && (forall replicaIdx, newViewMsg | && IsHonestReplica(c, replicaIdx) && var replicaVariables := v.hosts[replicaIdx].replicaVariables; && newViewMsg in replicaVariables.newViewMsgsRecvd.msgs :: && newViewMsg.payload.vcMsgs.msgs <= v.network.sentMsgs - && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs) - && newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs)) + && Messages.ValidNewViewMsg(c.clusterConfig, newViewMsg)) } predicate {:opaque} RecordedPrePreparesMatchHostView(c:Constants, v:Variables) { @@ -1574,19 +1568,15 @@ module Proof { && newViewMsg in replicaVariables'.newViewMsgsRecvd.msgs ensures && newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs - && MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs) { var replicaVariables := v.hosts[replicaIdx].replicaVariables; if (newViewMsg in replicaVariables.newViewMsgsRecvd.msgs) { assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; - assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); } else { if (h_step.RecvNewViewMsgStep?) { assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; - assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); } else if (h_step.SelectQuorumOfViewChangeMsgsStep?) { assert newViewMsg.payload.vcMsgs.msgs <= v'.network.sentMsgs; - assert MessagesAreFromReplicas(c, newViewMsg.payload.vcMsgs.msgs); } else { assert false; } @@ -1621,15 +1611,78 @@ module Proof { requires viewChangeMsg.payload.ViewChangeMsg? requires viewChangeMsg.sender == commitMsg.sender requires IsHonestReplica(c, commitMsg.sender) - requires commitMsg.payload.seqID > viewChangeMsg.payload.lastStableCheckpoint requires commitMsg.payload.view < viewChangeMsg.payload.newView ensures && commitMsg.payload.seqID in viewChangeMsg.payload.certificates && var certificate := viewChangeMsg.payload.certificates[commitMsg.payload.seqID]; && CertificateComportsWithCommit(c, certificate, commitMsg) { + assume commitMsg.payload.seqID > viewChangeMsg.payload.lastStableCheckpoint; // Because of TemporarilyDisabledCheckpointing. Move as requires once it is introduced reveal_EveryCommitMsgIsRememberedByVCMsgs(); } + lemma UnCommitableIfNoCertificates(c:Constants, + v:Variables, + replicaIdx:HostId, + seqID:Messages.SequenceID, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper, + newViewMsg:Message) + requires Inv(c, v) + requires IsHonestReplica(c, replicaIdx) + requires newViewMsg.payload.NewViewMsg? + requires priorView < newViewMsg.payload.newView + requires && var h_c := c.hosts[replicaIdx].replicaConstants; + && var h_v := v.hosts[replicaIdx].replicaVariables; + && newViewMsg in h_v.newViewMsgsRecvd.msgs + && Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) + && seqID in Replica.ExtractSeqIDsFromPrevView(h_c, h_v, newViewMsg) + && |Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)| == 0; + + ensures UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper) + { + var h_c := c.hosts[replicaIdx].replicaConstants; + var h_v := v.hosts[replicaIdx].replicaVariables; + var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); + var highestViewCert := Replica.HighestViewPrepareCertificate(relevantCerts); + { + // doubleAgent reasonong similar to the below case. + } + } + + lemma UnCommitableIfSomeCertificates(c:Constants, + v:Variables, + replicaIdx:HostId, + seqID:Messages.SequenceID, + priorView:nat, + priorOperationWrapper:Messages.OperationWrapper, + newViewMsg:Message) + requires Inv(c, v) + requires IsHonestReplica(c, replicaIdx) + requires newViewMsg.payload.NewViewMsg? + requires priorView < newViewMsg.payload.newView + requires && var h_c := c.hosts[replicaIdx].replicaConstants; + && var h_v := v.hosts[replicaIdx].replicaVariables; + && newViewMsg in h_v.newViewMsgsRecvd.msgs + && Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) + && seqID in Replica.ExtractSeqIDsFromPrevView(h_c, h_v, newViewMsg) + && var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); + && |relevantCerts| > 0 + && Replica.HighestViewPrepareCertificate(relevantCerts).prototype().operationWrapper != priorOperationWrapper + ensures UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper) + { + var h_c := c.hosts[replicaIdx].replicaConstants; + var h_v := v.hosts[replicaIdx].replicaVariables; + var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); + var highestViewCert := Replica.HighestViewPrepareCertificate(relevantCerts); + if highestViewCert.prototype().view == priorView { + // doubleAgent reasonong here. + } else if highestViewCert.prototype().view > priorView { + // From the highestViewCert take a prepare message from an honest replica. That honest replica has a NewViewMsg - use it to recurse. + } else if highestViewCert.prototype().view < priorView { + // doubleAgent reasonong here. + } + } + lemma HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare(c: Constants, v:Variables, v':Variables, step:Step, h_v:Replica.Variables, h_step:Replica.Step) requires Inv(c, v) requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) @@ -1654,118 +1707,36 @@ module Proof { if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { // Interesting case is when we record a new PrePrepare. var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs // The checks that the Replica does before recording && msg.payload.newView == h_v.view; // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare - - var newView := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value.payload.view; - if priorView + 1 == newView { - if |newViewMsgs| == 0 { - assert h_v.view == 0; - assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. - } - var newViewMsg :| newViewMsg in newViewMsgs; - var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); - var troubleMakers := ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper); - if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { - // Contradiction hypothesis - assert newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs) by { - reveal_RecordedNewViewMsgsAreChecked(); - } - UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); - assert viewChangers <= getAllReplicas(c) by { - reveal_RecordedNewViewMsgsAreChecked(); - } - var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); - assert !c.clusterConfig.IsFaultyReplica(doubleAgent); - assert doubleAgent in viewChangers; - var vcMsg:Message :| && vcMsg in newViewMsg.payload.vcMsgs.msgs - && vcMsg.sender == doubleAgent; - assert vcMsg.payload.ViewChangeMsg?; - assert vcMsg in v.network.sentMsgs by { - reveal_RecordedNewViewMsgsAreChecked(); - } - - assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - reveal_HonestReplicasLeaveViewsBehind(); - assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; - } - - if (seqID !in vcMsg.payload.certificates) { - reveal_TemporarilyDisableCheckpointing(); - assume false; - } else if (vcMsg.payload.certificates[seqID].empty()) { - // Need help from Oded in this branch. - // We are looking for an Inv that says every ViewChange msg after a Commit mentions the Committed SeqID. - assume false; - } else { - var certificate := vcMsg.payload.certificates[seqID]; - var certView := certificate.prototype().view; // We need to differentiate the first hop of the VC!!! - var sentCommit := Network.Message(doubleAgent, - Messages.Commit(priorView, - seqID, - priorOperationWrapper)); - if certView < priorView { - assert !ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { - if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { // Contradiction - // viewchangemessages from honest sender comport with uncommitable in view - //reveal_SentViewChangesMsgsComportWithUncommitableInView(); - //assume false; - //reveal_CommitMsgsFromHonestSendersAgree(); - assert vcMsg in v.network.sentMsgs; - var doubleAgentCommitMsg := Network.Message(doubleAgent, - Messages.Commit(priorView, - seqID, - priorOperationWrapper)); - assert doubleAgentCommitMsg in v.network.sentMsgs; - //reveal_EveryCommitMsgIsRememberedByVCMsgs(); - assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. - //assert CertificateComportsWithCommit(c, certificate, sentCommit); - reveal_TemporarilyDisableCheckpointing(); - InvokeEveryCommitMsgIsRememberedByVCMsgs(c, v, sentCommit, vcMsg); - assert false; - } - } - assert doubleAgent !in troubleMakers; - assert false; - } else if certView == priorView { - // Interesting branch - if there was a commit its operation wraper has to match the prepared certificate. - if ReplicaSentCommit(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) { - // operation wrapers agree - assert sentCommit in v.network.sentMsgs; - assume EveryCommitMsgIsRememberedByVCMsgs(c, v);// TODO: put this predicate in the Inv. - reveal_TemporarilyDisableCheckpointing(); - InvokeEveryCommitMsgIsRememberedByVCMsgs(c, v, sentCommit, vcMsg); - assert sentCommit.payload.operationWrapper == certificate.prototype().operationWrapper; - // Left off here, every other certificate has to be for the same veiw, - // therefore whichever certificate the new view chose, it must be from - // the same view as the double agent certificate. The certs have to - // agree on the operation wrapper by prepare quorum intersection. - assert false; - } else { - assert doubleAgent !in troubleMakers; - assert false; - } - } else { - // A valid ViewChange msg cannot present a certificate for a view past the one we are ViewChangig - assert false; - } - } - assert false; // Contradction is shown - } - var prePrepareMsg := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; - assert prePrepareMsg.payload.seqID == seqID; - assert |ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper)| < c.clusterConfig.AgreementQuorum(); - assert UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + // Double check that we have everything we need for the var newViewMsgs + var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; + assert prePrepareMsg.payload.seqID == seqID; + var newView := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value.payload.view; // TODO: rename currentView + // Assert that newView == replicaVariables'.view + if |newViewMsgs| == 0 { + assert h_v.view == 0; + assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. + } + var newViewMsg :| newViewMsg in newViewMsgs; + var h_c := c.hosts[replicaIdx].replicaConstants; + assert Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) by { + reveal_RecordedNewViewMsgsAreChecked(); + } + if seqID !in Replica.ExtractSeqIDsFromPrevView(h_c, h_v, newViewMsg) { + assume false; // Sliding of the WW is disabled for now. + } else if |Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)| == 0 { + UnCommitableIfNoCertificates(c, v, replicaIdx, seqID, priorView, priorOperationWrapper, newViewMsg); } else { - // call back to inductive step - // certView > priorView // Use the mutual induction hypothesis to get UncommitableInView directly. - assume false; + var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); + var highestViewCert := Replica.HighestViewPrepareCertificate(relevantCerts); + assert prePrepareMsg.payload.operationWrapper == highestViewCert.prototype().operationWrapper; + UnCommitableIfSomeCertificates(c, v, replicaIdx, seqID, priorView, priorOperationWrapper, newViewMsg); } - } else { - var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; - assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); - Library.SubsetCardinality(ReplicasThatCanCommitInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper), - ReplicasThatCanCommitInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper)); + assume false; } + assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); + assert ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper) // Carry the knowledge from v into v' + == ReplicasThatCanCommitInView(c, v, seqID, priorView, priorOperationWrapper); } } diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index a2cd75d547..2e0fb1dad3 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -243,6 +243,20 @@ module Replica { else HighestStable(c, rest) } + function getRelevantPrepareCertificates(c:Constants, seqID:SequenceID, newViewMsg:Network.Message) : (set) + requires c.WF() + requires newViewMsg.payload.NewViewMsg? + requires newViewMsg.payload.valid(c.clusterConfig) + { + set viewChangeMsg, cert:PreparedCertificate | + && viewChangeMsg in newViewMsg.payload.vcMsgs.msgs + && seqID in viewChangeMsg.payload.certificates + && cert == viewChangeMsg.payload.certificates[seqID] + && cert.WF() + && !cert.empty() + :: cert + } + function CalculateRestrictionForSeqID(c:Constants, v:Variables, seqID:SequenceID, newViewMsg:Network.Message) : Option // returns None if any operation is allowed requires CurrentNewViewMsg(c, v, newViewMsg) @@ -258,13 +272,7 @@ module Replica { if seqID !in ExtractSeqIDsFromPrevView(c, v, newViewMsg) then None else - var relevantPrepareCertificates := set viewChangeMsg, cert | - && viewChangeMsg in newViewMsg.payload.vcMsgs.msgs - && seqID in viewChangeMsg.payload.certificates - && cert == viewChangeMsg.payload.certificates[seqID] - && cert.WF() - && !cert.empty() - :: cert; + var relevantPrepareCertificates := getRelevantPrepareCertificates(c, seqID, newViewMsg); if |relevantPrepareCertificates| == 0 then Some(Noop) @@ -301,11 +309,9 @@ module Replica { newViewMsg:Network.Message) { && v.WF(c) - && newViewMsg.payload.NewViewMsg? - && newViewMsg.payload.valid(c.clusterConfig) && newViewMsg in v.newViewMsgsRecvd.msgs + && ValidNewViewMsg(c.clusterConfig, newViewMsg) && newViewMsg.payload.newView == v.view - && CurrentPrimary(c, v) == newViewMsg.sender } predicate IsValidOperationWrapper(c:Constants, From 8138f5f5d7f52f0bb764539cc1b1026aaa175a78 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Tue, 2 May 2023 19:14:04 +0300 Subject: [PATCH 35/38] HonestRecvPrePrepareStepPreservesUnCommitableAgreesWithRecordedPrePrepare lemma proof complete. Next steps proof UnCommitableIfNoCertificates and UnCommitableIfSomeCertificates since the above lemma relies on them being true. Refactors in Replica to provide deterministic definitions from chose operator. --- docs/sbft-formal-model/proof.dfy | 21 ++++++++++++--------- docs/sbft-formal-model/replica.i.dfy | 20 +++++++++++++++++--- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index ed77d4f263..5cc8e2a77b 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1704,9 +1704,9 @@ module Proof { && UnCommitableInView(c, v', prePrepareMsg.payload.seqID, priorView, priorOperationWrapper) { var replicaVariables := v.hosts[replicaIdx].replicaVariables; var replicaVariables' := v'.hosts[replicaIdx].replicaVariables; - if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { // Interesting case is when we record a new PrePrepare. - var newViewMsgs := set msg | && msg in h_v.newViewMsgsRecvd.msgs // The checks that the Replica does before recording - && msg.payload.newView == h_v.view; // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare + if replicaVariables.workingWindow.prePreparesRcvd[seqID].None? { // Interesting case is when we record a new PrePrepare. // The checks that the Replica does before recording // is sufficient to proove UnCommitableAgreesWithRecordedPrePrepare + var h_c := c.hosts[replicaIdx].replicaConstants; + var newViewMsgs := Replica.GetNewViewMsgsForCurrentView(h_c, h_v); // Double check that we have everything we need for the var newViewMsgs var prePrepareMsg := replicaVariables'.workingWindow.prePreparesRcvd[seqID].value; assert prePrepareMsg.payload.seqID == seqID; @@ -1716,8 +1716,7 @@ module Proof { assert h_v.view == 0; assert false;//This cannot happen because there is a priorView < prePrepareMsg's view. } - var newViewMsg :| newViewMsg in newViewMsgs; - var h_c := c.hosts[replicaIdx].replicaConstants; + var newViewMsg := Replica.GetNewViewMsgForCurrentView(h_c, h_v); assert Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) by { reveal_RecordedNewViewMsgsAreChecked(); } @@ -1726,13 +1725,17 @@ module Proof { } else if |Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)| == 0 { UnCommitableIfNoCertificates(c, v, replicaIdx, seqID, priorView, priorOperationWrapper, newViewMsg); } else { - var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); - var highestViewCert := Replica.HighestViewPrepareCertificate(relevantCerts); - assert prePrepareMsg.payload.operationWrapper == highestViewCert.prototype().operationWrapper; UnCommitableIfSomeCertificates(c, v, replicaIdx, seqID, priorView, priorOperationWrapper, newViewMsg); } } else { - assume false; + var prePrepareMsg := v.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + var prePrepareMsg' := v'.hosts[replicaIdx].replicaVariables.workingWindow.prePreparesRcvd[seqID].value; + assert prePrepareMsg == prePrepareMsg'; + assert seqID == prePrepareMsg'.payload.seqID by { + reveal_RecordedPrePreparesRecvdCameFromNetwork(); + } + assert UnCommitableInView(c, v, prePrepareMsg.payload.seqID, priorView, priorOperationWrapper); + assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); } assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); assert ReplicasThatCanCommitInView(c, v', seqID, priorView, priorOperationWrapper) // Carry the knowledge from v into v' diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index 2e0fb1dad3..bb4ef718cf 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -329,6 +329,21 @@ module Replica { && restriction.Some? ==> restriction.value == operation } + function GetNewViewMsgsForCurrentView(c:Constants, v:Variables) : set> + requires v.WF(c) + { + set msg | && msg in v.newViewMsgsRecvd.msgs + && msg.payload.newView == v.view + } + + function GetNewViewMsgForCurrentView(c:Constants, v:Variables) : Network.Message + requires v.WF(c) + requires |GetNewViewMsgsForCurrentView(c, v)| > 0 + { + var newViewMsg :| newViewMsg in GetNewViewMsgsForCurrentView(c, v); + newViewMsg + } + // For clarity here we have extracted all preconditions that must hold for a Replica to accept a PrePrepare predicate IsValidPrePrepare(c:Constants, v:Variables, msg:Network.Message) { @@ -340,12 +355,11 @@ module Replica { && msg.payload.view == v.view && msg.sender == CurrentPrimary(c, v) && v.workingWindow.prePreparesRcvd[msg.payload.seqID].None? - && var newViewMsgs := set msg | && msg in v.newViewMsgsRecvd.msgs - && msg.payload.newView == v.view; + && var newViewMsgs := GetNewViewMsgsForCurrentView(c, v); && (if |newViewMsgs| == 0 then true else && |newViewMsgs| == 1 - && var newViewMsg :| newViewMsg in newViewMsgs; + && var newViewMsg := GetNewViewMsgForCurrentView(c, v); && newViewMsg.payload.valid(c.clusterConfig) && c.clusterConfig.PrimaryForView(newViewMsg.payload.newView) == newViewMsg.sender && IsValidOperationWrapper(c, v, msg.payload.seqID, newViewMsg, msg.payload.operationWrapper)) From cd6b389b0a141b809333e3d8dc14c5c66c86c4ff Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 3 May 2023 11:08:33 +0300 Subject: [PATCH 36/38] Progress in proving UnCommitableIfNoCertificates. --- docs/sbft-formal-model/proof.dfy | 47 +++++++++++++++++++++++++--- docs/sbft-formal-model/replica.i.dfy | 4 +-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 5cc8e2a77b..3e04fd237c 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1630,9 +1630,10 @@ module Proof { requires Inv(c, v) requires IsHonestReplica(c, replicaIdx) requires newViewMsg.payload.NewViewMsg? - requires priorView < newViewMsg.payload.newView + requires priorView < newViewMsg.payload.newView requires && var h_c := c.hosts[replicaIdx].replicaConstants; && var h_v := v.hosts[replicaIdx].replicaVariables; + && newViewMsg == Replica.GetNewViewMsgForCurrentView(h_c, h_v) && newViewMsg in h_v.newViewMsgsRecvd.msgs && Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) && seqID in Replica.ExtractSeqIDsFromPrevView(h_c, h_v, newViewMsg) @@ -1643,10 +1644,48 @@ module Proof { var h_c := c.hosts[replicaIdx].replicaConstants; var h_v := v.hosts[replicaIdx].replicaVariables; var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); - var highestViewCert := Replica.HighestViewPrepareCertificate(relevantCerts); - { - // doubleAgent reasonong similar to the below case. + var newViewMsg := Replica.GetNewViewMsgForCurrentView(h_c, h_v); + var viewChangers := Messages.sendersOf(newViewMsg.payload.vcMsgs.msgs); + assert newViewMsg.payload.checked(c.clusterConfig, v.network.sentMsgs) by { + reveal_RecordedNewViewMsgsAreChecked(); } + UniqueSendersCardinality(newViewMsg.payload.vcMsgs.msgs); + assert viewChangers <= getAllReplicas(c) by { + reveal_RecordedNewViewMsgsAreChecked(); + } + var troubleMakers := ReplicasThatCanCommitInView(c, v, seqID, priorView, priorOperationWrapper); + if |troubleMakers| >= c.clusterConfig.AgreementQuorum() { + var doubleAgent := FindQuorumIntersection(c, viewChangers, troubleMakers); + var vcMsg:Message :| && vcMsg in newViewMsg.payload.vcMsgs.msgs + && vcMsg.sender == doubleAgent; + assert IsHonestReplica(c, doubleAgent); + assert !ReplicasInViewOrLower(c, v, seqID, priorView, priorOperationWrapper, doubleAgent) by { + reveal_HonestReplicasLeaveViewsBehind(); + assert v.hosts[doubleAgent].replicaVariables.view >= vcMsg.payload.newView; + } + var sentCommit := Network.Message(doubleAgent, + Messages.Commit(priorView, + seqID, + priorOperationWrapper)); + assert sentCommit in v.network.sentMsgs; + assume seqID in vcMsg.payload.certificates; // Sliding of the WW is disabled for now. + var certificate := vcMsg.payload.certificates[seqID]; + assert CertificateComportsWithCommit(c, certificate, sentCommit) by { + assume false; // Left off on a timeout + //reveal_EveryCommitMsgIsRememberedByVCMsgs(); + assert sentCommit.payload.seqID in vcMsg.payload.certificates; + assert sentCommit.payload.seqID == seqID; + assert CertificateComportsWithCommit(c, certificate, sentCommit); + } + assert !certificate.empty(); + assert certificate in Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); + Library.SubsetCardinality({certificate}, Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)); + assert |{certificate}| == 1; + assert |Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)| > 0; + assert |Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg)| == 0; + assert false; // Contradiction + } + assert UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper); } lemma UnCommitableIfSomeCertificates(c:Constants, diff --git a/docs/sbft-formal-model/replica.i.dfy b/docs/sbft-formal-model/replica.i.dfy index bb4ef718cf..9b7b948e81 100644 --- a/docs/sbft-formal-model/replica.i.dfy +++ b/docs/sbft-formal-model/replica.i.dfy @@ -309,9 +309,9 @@ module Replica { newViewMsg:Network.Message) { && v.WF(c) - && newViewMsg in v.newViewMsgsRecvd.msgs && ValidNewViewMsg(c.clusterConfig, newViewMsg) - && newViewMsg.payload.newView == v.view + && |GetNewViewMsgsForCurrentView(c, v)| > 0 + && newViewMsg == GetNewViewMsgForCurrentView(c, v) } predicate IsValidOperationWrapper(c:Constants, From 928f30e76e613e9bf85448c48ba208770e4b0a9d Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Thu, 4 May 2023 17:46:12 +0300 Subject: [PATCH 37/38] Summary for progress so far. --- docs/sbft-formal-model/README.md | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/docs/sbft-formal-model/README.md b/docs/sbft-formal-model/README.md index 23cb4a30c4..f3b31b1ef3 100644 --- a/docs/sbft-formal-model/README.md +++ b/docs/sbft-formal-model/README.md @@ -1,4 +1,5 @@ -# Project overview +## Project overview + The aim of this sub-project is to both document and to formally specify the concord-bft state machine replication (SMR) protocol. Given the distributed nature of the concord-bft protocol, unit tests offer limited means of establishing the desired state machine replication (SMR) safety properties. The main tool we use for proving correctness is an integration testing framework code-named [Apollo](https://github.com/vmware/concord-bft/tree/master/tests/apollo) which creates mini-blockchain deployments and exercises various runtime scenarios (including straight case, replica failures, network faults, as well as certain byzantine cases). @@ -7,7 +8,7 @@ Apollo does give us good confidence as to the correctness and fault tolerance of We use Dafny and first-order logic to model and prove the safety properties of the protocol's core state machine (liveness properties are out of scope for now). -# Dev instructions +## Dev instructions The Dafny model can be used as a document, for proving SBFT correctness properties, or as input for property-based tests of the implementation's core components. @@ -27,6 +28,7 @@ newgrp docker ``` ## Pull the image. + (This is the slow thing; it includes a couple GB of Ubuntu.) ```bash @@ -69,6 +71,28 @@ If everything is working, you should see something like: Dafny program verifier finished with 10 verified, 0 errors ``` -# Acknowledgements and references +## Acknowledgements and references Special thanks to [@jonhnet](https://github.com/jonhnet) for his help and support along the way. + +## Current status + +# We have completed the modeling of *replica.i.dfy* covering the following parts of the protocol: + +1. Slow Commit Path - the 2 phase commit path that requires collecting Prepared Certificate (2F+1 matching Prepares) and a Committed Certificate (2F+1 Commit messages). +2. View change - the mechanism that the system uses to replace the leader (Primary) in order to preserve livenes. +3. Sliding of the Working Window and Checkpointing - periodically the replicas exchange Checkpoint messages to reach agreement on the state. This also enables them to slide the Working Window of Sequence ID-s that can be considered for consensus in each replica. The checkpointing is crucial also for enabling replicas to catch up in case of long down time of a given replica. The Checkpoint represents a summary of the state (a hash in the implementation code and the entire state itself in the model) that can be used for correctness check when performing State Transfer. +4. State Transfer - the mechanism for replicas to help peers that have been disconnected for long enough to be unable to catch up because the system's Working Window has advanced beyond the particular replica's Working Window. This is a process that in the implementation code transfers portions of the state until catching up is completed in the behind replica. In the model we have the entire state in each Checkpoint message, therefore we can perform State Transfer once we collect quorum (2F+1) matching Checkpoint messages to the state in any of those messages (they have to be equal) if this state turns out to be more recent than our current state. We determine recentness based to the highest Sequence ID for which the system has reached consensus. + +# Modeling of malicious behavior in the system *faulty_replica.i.dfy*. + +To achieve validation in the presence of malicious behavior once we start writing the proof we needed a subset of the replicas (up to F) to act in a way that could try to corrupt the system. We achieve this by not restricting the F faulty replicas' actions in any specific way, thus allowing completely arbitrary behavior which can be considered as a super set of the Malicious behavior, which itself is a super set of the correct (honest) behavior that follows the protocol steps. + +# Message signatures checks support in *network.s.dfy*. + +In order for us to model signatures we have used the network to be a trusted arbitrary. It stores all the previously sent messages and can determine if a message really originated form a given host. The network rejects messages from hosts that try to impersonate another one. We also provide a mechanism to validate sub messages (some of the protocol messages are composite - carrying messages collected form peers in the model and hashes of those messages in the implementation code). + +# Progress on the proof. + +We have completed the proof that commit messages form honest senders agree in a given View, put otherwise, that consensus is reached in a View. This is a stepping stone for us to be able to prove consensus in the system is maintained even in the presence of multiple View Changes and at most F faulty replicas. The next milestone in proving UnCommitableAgreesWithPrepare. This predicate serves to bind the future and the past, showing that if an Hones Replica sends a Prepare message in a given View (effectively voting for a proposal from the primary for assigning a Client operation to a Sequence ID), it has performed all the necessary checks to validate that nothing else is commitable in prior views. +For the current state of the proof we have disabled sliding of the Working Window and Checkpointing in order to split the effort in smaller steps. \ No newline at end of file From 732591469a0a413b70cb87708a812ab73f7cfae8 Mon Sep 17 00:00:00 2001 From: Hristo Staykov Date: Wed, 17 May 2023 16:31:28 +0300 Subject: [PATCH 38/38] Insert assume false-s where the proof is in WIP. --- docs/sbft-formal-model/proof.dfy | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sbft-formal-model/proof.dfy b/docs/sbft-formal-model/proof.dfy index 3e04fd237c..f35e926104 100644 --- a/docs/sbft-formal-model/proof.dfy +++ b/docs/sbft-formal-model/proof.dfy @@ -1633,6 +1633,7 @@ module Proof { requires priorView < newViewMsg.payload.newView requires && var h_c := c.hosts[replicaIdx].replicaConstants; && var h_v := v.hosts[replicaIdx].replicaVariables; + && |Replica.GetNewViewMsgsForCurrentView(h_c, h_v)| > 0 && newViewMsg == Replica.GetNewViewMsgForCurrentView(h_c, h_v) && newViewMsg in h_v.newViewMsgsRecvd.msgs && Replica.CurrentNewViewMsg(h_c, h_v, newViewMsg) @@ -1709,6 +1710,7 @@ module Proof { && Replica.HighestViewPrepareCertificate(relevantCerts).prototype().operationWrapper != priorOperationWrapper ensures UnCommitableInView(c, v, seqID, priorView, priorOperationWrapper) { + assume false; // TODO: Proof in progress. To be removed. var h_c := c.hosts[replicaIdx].replicaConstants; var h_v := v.hosts[replicaIdx].replicaVariables; var relevantCerts := Replica.getRelevantPrepareCertificates(h_c, seqID, newViewMsg); @@ -1829,6 +1831,7 @@ module Proof { requires HonestReplicaStepTaken(c, v, v', step, h_v, h_step) ensures ViewChangeMsgsFromHonestInNetworkAreValid(c, v') { + assume false; // TODO: Proof in progress. To be removed. reveal_ViewChangeMsgsFromHonestInNetworkAreValid(); forall viewChangeMsg | && viewChangeMsg in v'.network.sentMsgs