nim-mysticeti/mysticeti/validator.nim
2024-12-09 18:07:43 +01:00

177 lines
6.2 KiB
Nim

import ./basics
import ./committee
import ./blocks
import ./validator/slots
import ./validator/rounds
import ./validator/checks
export slots
export checks
type Validator*[Dependencies] = ref object
identifier: Dependencies.Identifier
committee: Committee[Dependencies.Identifier]
membership: CommitteeMember
rounds: Rounds[Dependencies]
func new*[Dependencies](
_: type Validator[Dependencies],
identifier: Dependencies.Identifier,
committee: Committee[Dependencies.Identifier]
): Validator[Dependencies] =
without membership =? committee.membership(identifier):
raiseAssert "identity is not a member of the committee"
Validator[Dependencies](
identifier: identifier,
committee: committee,
membership: membership,
rounds: Rounds[Dependencies].init(committee.size)
)
func identifier*(validator: Validator): auto =
validator.identifier
func membership*(validator: Validator): CommitteeMember =
validator.membership
func round*(validator: Validator): uint64 =
validator.rounds.latest.number
func primaryProposer*(validator: Validator): CommitteeMember =
validator.rounds.latest.primaryProposer
func nextRound*(validator: Validator) =
validator.rounds.addNewRound()
func updateSkipped(validator: Validator, supporter: Validator.Dependencies.Block) =
func skips(blck: Validator.Dependencies.Block, round: uint64, author: CommitteeMember): bool =
for parent in blck.parents:
if parent.round == round and parent.author == author:
return false
true
if round =? validator.rounds.latest.find(supporter.round) and
previous =? round.previous:
for proposer in previous.proposers:
let slot = previous[proposer]
if supporter.skips(previous.number, proposer):
let author = supporter.author
let stake = validator.committee.stake(author)
slot.skipBy(author, stake)
func updateCertified(validator: Validator, certificate: Validator.Dependencies.Block) =
mixin id
without certifying =? validator.rounds.latest.find(certificate.round) and
voting =? certifying.previous and
proposing =? voting.previous:
return
for proposal in proposing.proposals:
var support: Voting
for vote in voting.proposals:
if proposal.blck.id in vote.blck.parents:
if vote.blck.id in certificate.parents:
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)
func addBlock(validator: Validator, signedBlock: SignedBlock) =
if round =? validator.rounds.latest.find(signedBlock.blck.round):
round.addProposal(signedBlock)
validator.updateSkipped(signedBlock.blck)
validator.updateCertified(signedBlock.blck)
func parentBlocks*(validator: Validator): auto =
mixin id
var parents: seq[BlockId[Validator.Dependencies.Hash]]
if previous =? validator.rounds.latest.previous:
for slot in previous.slots:
if slot.proposals.len == 1:
parents.add(slot.proposals[0].blck.id)
parents
func check*(validator: Validator, signed: SignedBlock): auto =
mixin id
type BlockCheck = checks.BlockCheck[SignedBlock.Dependencies]
type BlockId = blocks.BlockId[SignedBlock.Dependencies.Hash]
without member =? validator.committee.membership(signed.signer):
return BlockCheck.invalid("block is not signed by a committee member")
if member != signed.blck.author:
return BlockCheck.invalid("block is not signed by its author")
if signed.blck.round > validator.round:
return BlockCheck.invalid("block has a round number that is too high")
for parent in signed.blck.parents:
if parent.round >= signed.blck.round:
return BlockCheck.invalid("block has a parent from an invalid round")
for i in 0..<signed.blck.parents.len:
for j in 0..<i:
if signed.blck.parents[i] == signed.blck.parents[j]:
return BlockCheck.invalid("block includes a parent more than once")
if signed.blck.round > 0:
var stake: Stake
for parent in signed.blck.parents:
if parent.round == signed.blck.round - 1:
stake += validator.committee.stake(parent.author)
if stake <= 2/3:
return BlockCheck.invalid(
"block does not include parents representing >2/3 stake from previous round"
)
var missing: seq[BlockId]
for parent in signed.blck.parents:
if parent.round >= validator.rounds.oldest.number:
if validator.rounds.latest.find(parent).isNone:
missing.add(parent)
if missing.len > 0:
return BlockCheck.incomplete(missing)
if validator.rounds.latest.find(signed.blck.id).isSome:
return BlockCheck.invalid("block already received")
BlockCheck.correct(signed)
func add*(validator: Validator, correct: CorrectBlock) =
validator.addBlock(correct.signedBlock)
func getBlock*(validator: Validator, id: BlockId): auto =
validator.rounds.latest.find(id)
func status*(validator: Validator, round: uint64, author: CommitteeMember): auto =
if round =? validator.rounds.oldest.find(round):
return some round[author].status
func updateIndirect(validator: Validator, slot: ProposerSlot, round: Round) =
without anchor =? round.findAnchor():
return
without anchorProposal =? anchor.proposal:
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.certify(anchorProposal)
return
without parentBlock =? round.find(parent):
raiseAssert "parent block not found"
todo.add(parentBlock.blck.parents)
slot.skip()
iterator committed*(validator: Validator): auto =
var done = false
while not done:
let round = validator.rounds.oldest
for slot in round.slots:
if slot.status == SlotStatus.undecided:
validator.updateIndirect(slot, round)
case slot.status
of SlotStatus.undecided:
done = true
break
of SlotStatus.skip, SlotStatus.committed:
discard
of SlotStatus.commit:
yield slot.commit()
if not done:
validator.rounds.removeOldestRound()