diff --git a/mysticeti/validator.nim b/mysticeti/validator.nim index bc579fa..a8afe04 100644 --- a/mysticeti/validator.nim +++ b/mysticeti/validator.nim @@ -20,6 +20,7 @@ type Proposal[Signing, Hashing] = ref object blck: Block[Signing, Hashing] certifiedBy: Stake + certificates: seq[BlockId[Signing, Hashing]] SlotStatus* {.pure.} = enum undecided skip @@ -112,6 +113,7 @@ func updateCertified(validator: Validator, certificate: Block) = support += validator.committee.stake(vote.blck.author) if support > 2/3: proposal.certifiedBy += validator.committee.stake(certificate.author) + proposal.certificates.add(certificate.id) if proposal.certifiedBy > 2/3: proposerSlot.status = SlotStatus.commit @@ -153,12 +155,60 @@ func status*(validator: Validator, blck: Block): ?SlotStatus = func status*(validator: Validator, proposal: SignedBlock): ?SlotStatus = validator.status(proposal.blck) +func findAnchor(validator: Validator, round: Round): auto = + var next = round.next.?next.?next + while current =? next: + for member in validator.committee.ordered(current.number): + let slot = current[member] + if slot.status in [SlotStatus.undecided, SlotStatus.commit]: + return some slot + next = current.next + +func searchBackwards(round: Round, blockId: BlockId): auto = + var current = round + while current.number > blockId.round and previous =? current.previous: + current = previous + if current.number == blockId.round: + let slot = current[blockId.author] + for proposal in slot.proposals: + let blck = proposal.blck + if blck.id == blockId: + return some blck + +func certifiedProposal(slot: ProposerSlot): auto = + if slot.status in [SlotStatus.commit, SlotStatus.committed]: + for proposal in slot.proposals: + if proposal.certifiedBy > 2/3: + return some proposal + +func updateIndirect(validator: Validator, slot: ProposerSlot, round: Round) = + without anchor =? validator.findAnchor(round): + return + without anchorProposal =? anchor.certifiedProposal: + return + var todo = anchorProposal.blck.parents + while todo.len > 0: + let parent = todo.pop() + if parent.round < round.number + 2: + continue + for slotProposal in slot.proposals: + if parent in slotProposal.certificates: + slotProposal.certifiedBy = anchorProposal.certifiedBy + slot.status = SlotStatus.commit + return + without parentBlock =? round.searchBackwards(parent): + discard + todo.add(parentBlock.parents) + slot.status = SlotStatus.skip + iterator committed*(validator: Validator): auto = var done = false var current = some validator.first while not done and round =? current: for member in validator.committee.ordered(round.number): let slot = round[member] + if slot.status == SlotStatus.undecided: + validator.updateIndirect(slot, round) case slot.status of SlotStatus.undecided: done = true @@ -167,9 +217,9 @@ iterator committed*(validator: Validator): auto = discard of SlotStatus.commit: slot.status = SlotStatus.committed - for proposal in slot.proposals: - if proposal.certifiedBy > 2/3: - yield proposal.blck + without proposal =? slot.certifiedProposal: + raiseAssert "slot state is 'commit', but no proposal is certified" + yield proposal.blck if not done: validator.remove(round) current = round.next diff --git a/tests/mysticeti/testCommittee.nim b/tests/mysticeti/testCommittee.nim index b192850..9aa3233 100644 --- a/tests/mysticeti/testCommittee.nim +++ b/tests/mysticeti/testCommittee.nim @@ -84,3 +84,36 @@ suite "Commitee of Validators": discard exchangeProposals() check toSeq(validators[0].committed()) == second + test "commits blocks using the indirect decision rule": + # first round: proposal is seen by majority + let proposal = validators[0].propose(seq[Transaction].example) + for index in 1..3: + discard validators[index].propose(seq[Transaction].example) + validators[1].receive(proposal) + validators[2].receive(proposal) + # second round: majority votes are only seen by first validator + nextRound() + discard validators[0].propose(seq[Transaction].example) + let vote2 = validators[1].propose(seq[Transaction].example) + let vote3 = validators[2].propose(seq[Transaction].example) + discard validators[3].propose(seq[Transaction].example) + validators[0].receive(vote2) + validators[0].receive(vote3) + # third round: only first validator creates a certificate + nextRound() + let certificate = validators[0].propose(seq[Transaction].example) + for index in 1..3: + discard validators[index].propose(seq[Transaction].example) + validators[1].receive(certificate) + validators[2].receive(certificate) + validators[3].receive(certificate) + # fourth round: anchors + nextRound() + discard exchangeProposals() + # fifth round: voting on anchors + nextRound() + discard exchangeProposals() + # sixth round: certifying anchors + nextRound() + discard exchangeProposals() + check toSeq(validators[0].committed()).?[0] == some proposal.blck