diff --git a/mysticeti.nim b/mysticeti.nim index 1d56e45..b83a556 100644 --- a/mysticeti.nim +++ b/mysticeti.nim @@ -15,6 +15,7 @@ export validator.committed import ./mysticeti/committee export committee.Committee +export committee.Stake export committee.new export committee.CommitteeMember export committee.`==` diff --git a/mysticeti/validator/slots.nim b/mysticeti/validator/slots.nim index 902e32b..a9167a8 100644 --- a/mysticeti/validator/slots.nim +++ b/mysticeti/validator/slots.nim @@ -22,8 +22,6 @@ func proposals*(slot: ProposerSlot): auto = slot.proposals func proposal*(slot: ProposerSlot): auto = - if slot.proposals.len == 1: - return some slot.proposals[0] if slot.status in [SlotStatus.commit, SlotStatus.committed]: for proposal in slot.proposals: if proposal.certifiedBy > 2/3: diff --git a/tests/mysticeti/validator/testSlots.nim b/tests/mysticeti/validator/testSlots.nim new file mode 100644 index 0000000..754dd76 --- /dev/null +++ b/tests/mysticeti/validator/testSlots.nim @@ -0,0 +1,96 @@ +import ../basics +import mysticeti +import mysticeti/validator/slots + +suite "Proposer Slots": + + type Block = mysticeti.Block[MockHashing] + type BlockId = mysticeti.BlockId[MockHashing] + type Proposal = slots.Proposal[MockHashing] + type ProposerSlot = slots.ProposerSlot[MockHashing] + + var slot: ProposerSlot + + setup: + slot = ProposerSlot.new() + + test "slots are undecided by default": + check slot.status == SlotStatus.undecided + + test "slots have no proposals initially": + check slot.proposals.len == 0 + + test "slots have not chosen a proposal initially": + check slot.proposal == none Proposal + + test "blocks can be added to slots, and they become proposals": + let blocks = seq[Block].example + for blck in blocks: + slot.add(blck) + for blck in blocks: + check slot.proposals.anyIt(it.blck == blck) + + test "proposals have no certificates initially": + slot.add(Block.example) + let proposal = slot.proposals[0] + check proposal.certificates.len == 0 + + test "proposals can be certified by other blocks": + slot.add(Block.example) + let proposal = slot.proposals[0] + let certificate1, certificate2 = BlockId.example + proposal.certifyBy(certificate1, Stake(1/9)) + proposal.certifyBy(certificate2, Stake(2/9)) + check proposal.certificates == @[certificate1, certificate2] + + test "slots can be committed when a proposal is certified by >2/3 stake": + slot.add(Block.example) + let proposal = slot.proposals[0] + proposal.certifyBy(BlockId.example, 1/3) + check slot.status == SlotStatus.undecided + proposal.certifyBy(BlockId.example, 1/3) + check slot.status == SlotStatus.undecided + proposal.certifyBy(BlockId.example, 1/1000) + check slot.status == SlotStatus.commit + + test "slots choose a proposal when it is certified by >2/3 stake": + slot.add(Block.example) + slot.add(Block.example) + let proposal = slot.proposals[1] + proposal.certifyBy(BlockId.example, 1/3) + check slot.proposal == none Proposal + proposal.certifyBy(BlockId.example, 1/3) + check slot.proposal == none Proposal + proposal.certifyBy(BlockId.example, 1/1000) + check slot.proposal == some proposal + + test "proposals can be certified by an anchor": + let anchor = ProposerSlot.new() + anchor.add(Block.example) + anchor.proposals[0].certifyBy(BlockId.example, 3/4) + slot.add(Block.example) + let proposal = slot.proposals[0] + proposal.certify(!anchor.proposal) + check slot.status == SlotStatus.commit + check slot.proposal == some proposal + + test "committing a slot marks it as committed and returns the chosen block": + let block1, block2 = Block.example + slot.add(block1) + slot.add(block2) + let proposal = slot.proposals[1] + proposal.certifyBy(BlockId.example, 3/4) + check slot.commit() == block2 + check slot.status == SlotStatus.committed + + test "slots can be skipped when >2/3 stake skip it": + slot.skipBy(1/3) + check slot.status == SlotStatus.undecided + slot.skipBy(1/3) + check slot.status == SlotStatus.undecided + slot.skipBy(1/1000) + check slot.status == SlotStatus.skip + + test "slots can be skipped immediately": + slot.skip() + check slot.status == SlotStatus.skip diff --git a/tests/tests.nim b/tests/tests.nim index c79a37e..903ed24 100644 --- a/tests/tests.nim +++ b/tests/tests.nim @@ -1,5 +1,6 @@ import ./mysticeti/testCommittee import ./mysticeti/testBlocks +import ./mysticeti/validator/testSlots import ./mysticeti/validator/testSingle import ./mysticeti/validator/testMultiple