mirror of
https://github.com/logos-storage/nim-mysticeti.git
synced 2026-01-04 06:33:11 +00:00
use threshold logical clock to move to next round
This commit is contained in:
parent
742d9e8334
commit
22b473763c
14
Readme.md
14
Readme.md
@ -183,18 +183,12 @@ if checked.verdict == BlockVerdict.incomplete:
|
||||
|
||||
### Moving to the next round
|
||||
|
||||
The Mysticeti protocol uses a threshold logical clock to move from one round to
|
||||
the next. This means that each validator moves to the next round when it's seen
|
||||
enough blocks in the current round to represent >2/3 of the stake.
|
||||
The validator uses a threshold logical clock to move from one round to the next.
|
||||
This means it moves to the next round when it's seen enough blocks in the
|
||||
current round to represent >2/3 of the stake.
|
||||
|
||||
Additionaly, the protocol mandates that all validators wait for the primary
|
||||
proposer of the round (with a timeout), before moving to the next round.
|
||||
|
||||
To move to the next round, invoke the `nextRound` function:
|
||||
|
||||
```nim
|
||||
validator.nextRound()
|
||||
```
|
||||
proposer of the round (with a timeout), before creating their own blocks.
|
||||
|
||||
The primary proposer for the current round can be retrieved from the validator:
|
||||
|
||||
|
||||
@ -11,7 +11,6 @@ export validator.identifier
|
||||
export validator.membership
|
||||
export validator.round
|
||||
export validator.primaryProposer
|
||||
export validator.nextRound
|
||||
export validator.parentBlocks
|
||||
export validator.check
|
||||
export validator.add
|
||||
|
||||
@ -13,6 +13,7 @@ type Validator*[Dependencies] = ref object
|
||||
committee: Committee[Dependencies.Identifier]
|
||||
membership: CommitteeMember
|
||||
rounds: Rounds[Dependencies]
|
||||
clockThreshold: Voting
|
||||
|
||||
func new*[Dependencies](
|
||||
_: type Validator[Dependencies],
|
||||
@ -40,9 +41,6 @@ func round*(validator: Validator): uint64 =
|
||||
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:
|
||||
@ -76,11 +74,22 @@ func updateCertified(validator: Validator, certificate: Validator.Dependencies.B
|
||||
let stake = validator.committee.stake(certificate.author)
|
||||
proposal.certifyBy(certificate.id, stake)
|
||||
|
||||
func updateRound(validator: Validator, blck: Validator.Dependencies.Block) =
|
||||
if blck.round == validator.round:
|
||||
let author = blck.author
|
||||
let stake = validator.committee.stake(author)
|
||||
validator.clockThreshold.add(author, stake)
|
||||
if validator.clockThreshold.stake > 2/3:
|
||||
validator.rounds.addNewRound()
|
||||
validator.clockThreshold.reset()
|
||||
|
||||
func addBlock(validator: Validator, signedBlock: SignedBlock) =
|
||||
if round =? validator.rounds.latest.find(signedBlock.blck.round):
|
||||
let blck = signedBlock.blck
|
||||
if round =? validator.rounds.latest.find(blck.round):
|
||||
round.addProposal(signedBlock)
|
||||
validator.updateSkipped(signedBlock.blck)
|
||||
validator.updateCertified(signedBlock.blck)
|
||||
validator.updateSkipped(blck)
|
||||
validator.updateCertified(blck)
|
||||
validator.updateRound(blck)
|
||||
|
||||
func parentBlocks*(validator: Validator): auto =
|
||||
mixin id
|
||||
@ -104,8 +113,6 @@ func check*(validator: Validator, signed: SignedBlock): auto =
|
||||
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")
|
||||
|
||||
@ -18,35 +18,30 @@ proc scenarioFigure4*(simulator: NetworkSimulator): ?!seq[seq[SignedBlock]] =
|
||||
2: @[0, 2, 3],
|
||||
3: @[1, 2, 3]
|
||||
})
|
||||
simulator.nextRound()
|
||||
proposals.add(? simulator.exchangeProposals {
|
||||
0: @[0, 1, 3],
|
||||
1: @[0, 1, 3],
|
||||
2: @[0, 3],
|
||||
3: @[1, 3]
|
||||
})
|
||||
simulator.nextRound()
|
||||
proposals.add(? simulator.exchangeProposals {
|
||||
0: @[2, 3, 0, 1],
|
||||
1: @[2, 3, 0, 1],
|
||||
|
||||
3: @[2, 3, 0, 1]
|
||||
})
|
||||
simulator.nextRound()
|
||||
proposals.add(? simulator.exchangeProposals {
|
||||
2: @[2, 3, 0, 1],
|
||||
3: @[3],
|
||||
0: @[2, 3, 0, 1],
|
||||
1: @[2, 3, 0, 1]
|
||||
})
|
||||
simulator.nextRound()
|
||||
proposals.add(? simulator.exchangeProposals {
|
||||
2: @[],
|
||||
3: @[2, 3, 0],
|
||||
0: @[2, 3, 0],
|
||||
1: @[2, 3, 0]
|
||||
})
|
||||
simulator.nextRound()
|
||||
proposals.add(? simulator.exchangeProposals {
|
||||
2: @[2, 3, 0, 1],
|
||||
3: @[2, 3, 0, 1],
|
||||
@ -74,5 +69,4 @@ proc randomScenario*(simulator: NetworkSimulator): ?!seq[seq[SignedBlock]] =
|
||||
receivers.add(receiver)
|
||||
exchanges.add( (proposer, receivers) )
|
||||
proposals.add(? simulator.exchangeProposals(exchanges))
|
||||
simulator.nextRound()
|
||||
success proposals
|
||||
|
||||
@ -26,10 +26,6 @@ func identities*(simulator: NetworkSimulator): seq[Identity] =
|
||||
func validators*(simulator: NetworkSimulator): seq[Validator] =
|
||||
simulator.validators
|
||||
|
||||
func nextRound*(simulator: NetworkSimulator) =
|
||||
for validator in simulator.validators:
|
||||
validator.nextRound()
|
||||
|
||||
proc propose*(simulator: NetworkSimulator, validatorIndex: int): SignedBlock =
|
||||
let validator = simulator.validators[validatorIndex]
|
||||
let identity = simulator.identities[validatorIndex]
|
||||
|
||||
@ -8,9 +8,7 @@ suite "Validator Network Performance":
|
||||
# TODO: 100 validators
|
||||
let simulator = NetworkSimulator.init(20)
|
||||
discard !simulator.exchangeProposals()
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
simulator.nextRound()
|
||||
let start = now()
|
||||
discard !simulator.exchangeProposals()
|
||||
let finish = now()
|
||||
|
||||
@ -21,28 +21,30 @@ suite "Validator Network":
|
||||
for validator in simulator.validators:
|
||||
check validator.round == 0
|
||||
|
||||
test "validators can move to next round":
|
||||
for validator in simulator.validators:
|
||||
validator.nextRound()
|
||||
check validator.round == 1
|
||||
validator.nextRound()
|
||||
validator.nextRound()
|
||||
check validator.round == 3
|
||||
test "validators move to the next round after receiving >2/3 stake proposals":
|
||||
let validator = simulator.validators[0]
|
||||
for round in 0'u64..10:
|
||||
check validator.round == round # 0/4 stake
|
||||
discard !simulator.exchangeProposals({0: @[0, 1, 2, 3]})
|
||||
check validator.round == round # 1/4 stake
|
||||
discard !simulator.exchangeProposals({1: @[0, 1, 2, 3]})
|
||||
check validator.round == round # 2/4 stake
|
||||
discard !simulator.exchangeProposals({2: @[0, 1, 2, 3]})
|
||||
check validator.round == round + 1 # 3/4 stake
|
||||
|
||||
test "primary proposer rotates on a round-robin schedule":
|
||||
check simulator.validators.allIt(it.primaryProposer == CommitteeMember(0))
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
check simulator.validators.allIt(it.primaryProposer == CommitteeMember(1))
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
check simulator.validators.allIt(it.primaryProposer == CommitteeMember(2))
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
check simulator.validators.allIt(it.primaryProposer == CommitteeMember(3))
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
check simulator.validators.allIt(it.primaryProposer == CommitteeMember(0))
|
||||
|
||||
test "validators expose blocks from previous round as parents":
|
||||
let previous = !simulator.exchangeProposals()
|
||||
simulator.nextRound()
|
||||
let parents = simulator.validators[0].parentBlocks
|
||||
for proposal in previous:
|
||||
check proposal.blck.id in parents
|
||||
@ -86,7 +88,6 @@ suite "Validator Network":
|
||||
let parents = (!simulator.exchangeProposals()).mapIt(it.blck.id)
|
||||
let badParentRound = 1'u64
|
||||
let badParent = BlockId.init(CommitteeMember(0), badParentRound, Hash.example)
|
||||
simulator.nextRound()
|
||||
let blck = Block.new(
|
||||
CommitteeMember(0),
|
||||
round = 1,
|
||||
@ -103,7 +104,6 @@ suite "Validator Network":
|
||||
test "refuses proposals that include a parent more than once":
|
||||
let parents = (!simulator.exchangeProposals()).mapIt(it.blck.id)
|
||||
let badParent = parents.sample
|
||||
simulator.nextRound()
|
||||
let blck = Block.new(
|
||||
CommitteeMember(0),
|
||||
round = 1,
|
||||
@ -119,7 +119,6 @@ suite "Validator Network":
|
||||
|
||||
test "refuses proposals without >2/3 parents from the previous round":
|
||||
let parents = (!simulator.exchangeProposals()).mapIt(it.blck.id)
|
||||
simulator.nextRound()
|
||||
let blck = Block.new(
|
||||
CommitteeMember(0),
|
||||
round = 1,
|
||||
@ -143,7 +142,6 @@ suite "Validator Network":
|
||||
3: @[0, 1, 2, 3],
|
||||
}
|
||||
# second round: validator 0 creates block with parent that others didn't see
|
||||
simulator.nextRound()
|
||||
let proposal = simulator.propose(0)
|
||||
# other validator will not accept block before it receives the parent
|
||||
let checked = simulator.validators[1].check(proposal)
|
||||
@ -160,8 +158,6 @@ suite "Validator Network":
|
||||
}
|
||||
# for the second to the sixth round, validator 0 is down
|
||||
for _ in 2..6:
|
||||
for validator in simulator.validators[1..3]:
|
||||
validator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[1, 2, 3],
|
||||
2: @[1, 2, 3],
|
||||
@ -170,18 +166,11 @@ suite "Validator Network":
|
||||
# validator 1 cleans up old blocks
|
||||
discard toSeq(simulator.validators[1].committed())
|
||||
# validator 0 comes back online and creates block for second round
|
||||
simulator.validators[0].nextRound()
|
||||
let proposal = simulator.propose(0)
|
||||
# validator 1 accepts block even though parent has already been cleaned up
|
||||
check simulator.validators[1].check(proposal).verdict == BlockVerdict.correct
|
||||
|
||||
test "refuses proposals with a round number that is too high":
|
||||
discard !simulator.exchangeProposals()
|
||||
simulator.validators[0].nextRound()
|
||||
let proposal = simulator.propose(0)
|
||||
let checked = simulator.validators[1].check(proposal)
|
||||
check checked.verdict == BlockVerdict.invalid
|
||||
check checked.reason == "block has a round number that is too high"
|
||||
check checked.verdict == BlockVerdict.correct
|
||||
simulator.validators[1].add(checked.blck)
|
||||
|
||||
test "refuses a proposal that was already received":
|
||||
let proposals = !simulator.exchangeProposals()
|
||||
@ -200,7 +189,6 @@ suite "Validator Network":
|
||||
let round = proposals[0].blck.round
|
||||
let author = proposals[0].blck.author
|
||||
# second round: voting
|
||||
simulator.nextRound()
|
||||
let votes = simulator.propose()
|
||||
simulator.validators[0].add(simulator.validators[0].check(votes[1]).blck)
|
||||
simulator.validators[0].add(simulator.validators[0].check(votes[2]).blck)
|
||||
@ -217,7 +205,6 @@ suite "Validator Network":
|
||||
3: @[0, 1, 2, 3]
|
||||
}
|
||||
# second round: first validator does not receive votes
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[1, 2, 3],
|
||||
2: @[1, 2, 3],
|
||||
@ -225,7 +212,6 @@ suite "Validator Network":
|
||||
}
|
||||
# third round: first validator receives certificates, and also the votes
|
||||
# from the previous round because they are the parents of the certificates
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[0, 1, 2, 3],
|
||||
2: @[0, 1, 2, 3],
|
||||
@ -241,10 +227,8 @@ suite "Validator Network":
|
||||
let round = proposal.blck.round
|
||||
let author = proposal.blck.author
|
||||
# second round: voting
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
# third round: certifying
|
||||
simulator.nextRound()
|
||||
let certificates = simulator.propose()
|
||||
simulator.validators[0].add(simulator.validators[0].check(certificates[1]).blck)
|
||||
check simulator.validators[0].status(round, author) == some SlotStatus.undecided
|
||||
@ -255,14 +239,12 @@ suite "Validator Network":
|
||||
# first round: proposing
|
||||
let proposals = !simulator.exchangeProposals()
|
||||
# second round: first validator does not receive votes
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[1, 2, 3],
|
||||
2: @[1, 2, 3],
|
||||
3: @[1, 2, 3]
|
||||
}
|
||||
# third round: first validator does not receive certificates
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[1, 2, 3],
|
||||
2: @[1, 2, 3],
|
||||
@ -270,7 +252,6 @@ suite "Validator Network":
|
||||
}
|
||||
# fourth round: first validator receives votes and certificates, because
|
||||
# they are the parents of the blocks from this round
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals {
|
||||
1: @[0, 1, 2, 3],
|
||||
2: @[0, 1, 2, 3],
|
||||
@ -283,15 +264,12 @@ suite "Validator Network":
|
||||
test "can iterate over the list of committed blocks":
|
||||
# blocks proposed in first round, in order of committee members
|
||||
let first = (!simulator.exchangeProposals()).mapIt(it.blck)
|
||||
simulator.nextRound()
|
||||
# blocks proposed in second round, round-robin order
|
||||
let second = (!simulator.exchangeProposals()).mapIt(it.blck).rotatedLeft(1)
|
||||
simulator.nextRound()
|
||||
# certify blocks from the first round
|
||||
discard !simulator.exchangeProposals()
|
||||
check toSeq(simulator.validators[0].committed()) == first
|
||||
# certify blocks from the second round
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
check toSeq(simulator.validators[0].committed()) == second
|
||||
|
||||
@ -342,9 +320,7 @@ suite "Validator Network":
|
||||
!exchangeBlock(simulator.validators[0], simulator.validators[2], proposalA)
|
||||
!exchangeBlock(simulator.validators[0], simulator.validators[3], proposalB)
|
||||
# next rounds happen normally
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
simulator.nextRound()
|
||||
discard !simulator.exchangeProposals()
|
||||
# check that only the proposal that was sent to the majority is committed
|
||||
for validator in simulator.validators:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user