count votes from committee members only once

This commit is contained in:
Mark Spanbroek 2024-10-30 14:50:00 +01:00
parent da66c27de7
commit 7ce6f639f3
9 changed files with 105 additions and 58 deletions

View File

@ -1,39 +1,7 @@
import ./basics
import ./signing
import ./committee/members
import ./committee/committee
import ./committee/voting
export members.CommitteeMember
export members.`==`
export members.`$`
type
Committee*[Signing] = ref object
members: seq[Identifier[Signing]]
stakes: seq[Stake]
Stake* = float64
func new*(_: type Committee, stakes: openArray[(Identifier, Stake)]): auto =
var committee = Committee[Identifier.Signing]()
for (member, stake) in stakes:
committee.members.add(member)
committee.stakes.add(stake)
committee
func size*(committee: Committee): int =
committee.members.len
func membership*(committee: Committee, identifier: Identifier): ?CommitteeMember =
let index = committee.members.find(identifier)
if index < 0:
none CommitteeMember
else:
some CommitteeMember(index)
func stake*(committee: Committee, member: CommitteeMember): Stake =
committee.stakes[int(member)]
func stake*(committee: Committee, identifier: Identifier): Stake =
if member =? committee.membership(identifier):
committee.stake(member)
else:
0
export members
export committee
export voting

View File

@ -0,0 +1,35 @@
import ../basics
import ../signing
import ./members
type
Committee*[Signing] = ref object
members: seq[Identifier[Signing]]
stakes: seq[Stake]
Stake* = float64
func new*(_: type Committee, stakes: openArray[(Identifier, Stake)]): auto =
var committee = Committee[Identifier.Signing]()
for (member, stake) in stakes:
committee.members.add(member)
committee.stakes.add(stake)
committee
func size*(committee: Committee): int =
committee.members.len
func membership*(committee: Committee, identifier: Identifier): ?CommitteeMember =
let index = committee.members.find(identifier)
if index < 0:
none CommitteeMember
else:
some CommitteeMember(index)
func stake*(committee: Committee, member: CommitteeMember): Stake =
committee.stakes[int(member)]
func stake*(committee: Committee, identifier: Identifier): Stake =
if member =? committee.membership(identifier):
committee.stake(member)
else:
0

View File

@ -0,0 +1,14 @@
import ./members
import ./committee
type Voting* = object
voted: seq[CommitteeMember]
stake: Stake
func add*(voting: var Voting, member: CommitteeMember, stake: Stake) =
if member notin voting.voted:
voting.voted.add(member)
voting.stake += stake
func stake*(voting: Voting): Stake =
voting.stake

View File

@ -49,8 +49,9 @@ func updateSkipped(validator: Validator, supporter: Block) =
for member in previous.members:
let slot = previous[member]
if supporter.skips(previous.number, member):
let stake = validator.committee.stake(supporter.author)
slot.skipBy(stake)
let author = supporter.author
let stake = validator.committee.stake(author)
slot.skipBy(author, stake)
func updateCertified(validator: Validator, certificate: Block) =
without certifying =? validator.rounds.latest.find(certificate.round) and
@ -58,12 +59,14 @@ func updateCertified(validator: Validator, certificate: Block) =
proposing =? voting.previous:
return
for proposal in proposing.proposals:
var support: Stake
var support: Voting
for vote in voting.proposals:
if proposal.blck.id in vote.blck.parents:
if vote.blck.id in certificate.parents:
support += validator.committee.stake(vote.blck.author)
if support > 2/3:
let author = vote.blck.author
let stake = validator.committee.stake(author)
support.add(author, stake)
if support.stake > 2/3:
let stake = validator.committee.stake(certificate.author)
proposal.certifyBy(certificate.id, stake)

View File

@ -5,12 +5,12 @@ import ../committee
type
ProposerSlot*[Signing, Hashing] = ref object
proposals: seq[Proposal[Signing, Hashing]]
skippedBy: Stake
skippedBy: Voting
status: SlotStatus
Proposal*[Signing, Hashing] = ref object
slot: ProposerSlot[Signing, Hashing]
signedBlock: SignedBlock[Signing, Hashing]
certifiedBy: Stake
certifiedBy: Voting
certificates: seq[BlockId[Hashing]]
SlotStatus* {.pure.} = enum
undecided
@ -24,7 +24,7 @@ func proposals*(slot: ProposerSlot): auto =
func proposal*(slot: ProposerSlot): auto =
if slot.status in [SlotStatus.commit, SlotStatus.committed]:
for proposal in slot.proposals:
if proposal.certifiedBy > 2/3:
if proposal.certifiedBy.stake > 2/3:
return some proposal
func status*(slot: ProposerSlot): auto =
@ -41,13 +41,14 @@ func certificates*(proposal: Proposal): auto =
func addProposal*(slot: ProposerSlot, signedBlock: SignedBlock) =
let proposal = Proposal[SignedBlock.Signing, SignedBlock.Hashing](
slot: slot, signedBlock: signedBlock
slot: slot,
signedBlock: signedBlock
)
slot.proposals.add(proposal)
func skipBy*(slot: ProposerSlot, stake: Stake) =
slot.skippedBy += stake
if slot.skippedBy > 2/3:
func skipBy*(slot: ProposerSlot, member: CommitteeMember, stake: Stake) =
slot.skippedBy.add(member, stake)
if slot.skippedBy.stake > 2/3:
slot.status = SlotStatus.skip
func skip*(slot: ProposerSlot) =
@ -57,20 +58,20 @@ func skip*(slot: ProposerSlot) =
func commit*(slot: ProposerSlot): auto =
assert slot.status == SlotStatus.commit
let proposal = !slot.proposal
assert proposal.certifiedBy > 2/3
assert proposal.certifiedBy.stake > 2/3
slot.status = SlotStatus.committed
return proposal.blck
func certifyBy*(proposal: Proposal, certificate: BlockId, stake: Stake) =
proposal.certificates.add(certificate)
proposal.certifiedBy += stake
if proposal.certifiedBy > 2/3:
proposal.certifiedBy.add(certificate.author, stake)
if proposal.certifiedBy.stake > 2/3:
proposal.slot.status = SlotStatus.commit
func certify*(proposal, anchor: Proposal) =
assert proposal.slot.status == SlotStatus.undecided
assert anchor.slot.status == SlotStatus.commit
assert anchor.certifiedBy > 2/3
assert anchor.certifiedBy.stake > 2/3
proposal.certificates = @[anchor.blck.id]
proposal.certifiedBy = anchor.certifiedBy
proposal.slot.status = SlotStatus.commit

View File

@ -1,4 +1,4 @@
import ./basics
import ../basics
import mysticeti/committee
import mysticeti/signing

View File

@ -0,0 +1,25 @@
import ../basics
import mysticeti
import mysticeti/committee
suite "Committee Voting":
test "by default 0 stake has voted":
var voting: Voting
check voting.stake == 0
test "when a member adds a vote, it add its stake to the total":
var voting: Voting
voting.add(CommitteeMember(0), 1/8)
check voting.stake == 1/8
voting.add(CommitteeMember(1), 1/2)
check voting.stake == 5/8
voting.add(CommitteeMember(3), 1/8)
check voting.stake == 3/4
test "votes are only counted once":
var voting: Voting
voting.add(CommitteeMember(0), 1/8)
check voting.stake == 1/8
voting.add(CommitteeMember(0), 1/8)
check voting.stake == 1/8

View File

@ -85,11 +85,11 @@ suite "Proposer Slots":
check slot.status == SlotStatus.committed
test "slots can be skipped when >2/3 stake skip it":
slot.skipBy(1/3)
slot.skipBy(CommitteeMember(0), 1/3)
check slot.status == SlotStatus.undecided
slot.skipBy(1/3)
slot.skipBy(CommitteeMember(1), 1/3)
check slot.status == SlotStatus.undecided
slot.skipBy(1/1000)
slot.skipBy(CommitteeMember(2), 1/1000)
check slot.status == SlotStatus.skip
test "slots can be skipped immediately":

View File

@ -1,5 +1,6 @@
import ./mysticeti/testCommittee
import ./mysticeti/testBlocks
import ./mysticeti/committee/testCommittee
import ./mysticeti/committee/testVoting
import ./mysticeti/validator/testSlots
import ./mysticeti/validator/testRound
import ./mysticeti/validator/testRounds