mirror of
https://github.com/fluencelabs/tendermint
synced 2025-06-15 14:21:22 +00:00
Added CommitTimeWait step, the state machine model changed a bit.
This commit is contained in:
@ -253,6 +253,13 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte
|
|||||||
}
|
}
|
||||||
conR.sw.Broadcast(StateCh, msg)
|
conR.sw.Broadcast(StateCh, msg)
|
||||||
}
|
}
|
||||||
|
// Maybe run RoundActionCommitWait.
|
||||||
|
if vote.Type == VoteTypeCommit &&
|
||||||
|
rs.Commits.HasTwoThirdsMajority() &&
|
||||||
|
rs.Step < RoundStepCommit {
|
||||||
|
// NOTE: Do not call RunAction*() methods here directly.
|
||||||
|
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionCommitWait}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -304,10 +311,15 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
case RoundStepPrecommit:
|
case RoundStepPrecommit:
|
||||||
// Wake up when the round is over.
|
// Wake up when the round is over.
|
||||||
time.Sleep(time.Duration(1.0-elapsedRatio) * roundDuration)
|
time.Sleep(time.Duration(1.0-elapsedRatio) * roundDuration)
|
||||||
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionCommit}
|
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionNextRound}
|
||||||
case RoundStepCommit:
|
case RoundStepCommit:
|
||||||
|
panic("Should not happen: RoundStepCommit waits until +2/3 commits.")
|
||||||
|
case RoundStepCommitWait:
|
||||||
// Wake up when it's time to finalize commit.
|
// Wake up when it's time to finalize commit.
|
||||||
time.Sleep(time.Duration(1.0-elapsedRatio)*roundDuration + finalizeDuration)
|
if rs.CommitTime.IsZero() {
|
||||||
|
panic("RoundStepCommitWait requires rs.CommitTime")
|
||||||
|
}
|
||||||
|
time.Sleep(rs.CommitTime.Sub(time.Now()) + finalizeDuration)
|
||||||
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionFinalize}
|
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionFinalize}
|
||||||
default:
|
default:
|
||||||
panic("Should not happen")
|
panic("Should not happen")
|
||||||
@ -316,6 +328,11 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
|
|
||||||
scheduleNextAction()
|
scheduleNextAction()
|
||||||
|
|
||||||
|
// NOTE: All ConsensusState.RunAction*() calls must come from here.
|
||||||
|
// Since only one routine calls them, it is safe to assume that
|
||||||
|
// the RoundState Height/Round/Step won't change concurrently.
|
||||||
|
// However, other fields like Proposal could change, due to gossip.
|
||||||
|
ACTION_LOOP:
|
||||||
for {
|
for {
|
||||||
roundAction := <-conR.doActionCh
|
roundAction := <-conR.doActionCh
|
||||||
|
|
||||||
@ -334,18 +351,30 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
conR.sw.Broadcast(StateCh, msg)
|
conR.sw.Broadcast(StateCh, msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
if height != rs.Height || round != rs.Round {
|
// Continue if action is not relevant
|
||||||
// Action is not relevant
|
if height != rs.Height {
|
||||||
// This may happen if an external routine
|
continue
|
||||||
// pushes an action to conR.doActionCh.
|
}
|
||||||
return
|
// If action >= RoundActionCommit, the round doesn't matter.
|
||||||
|
if action < RoundActionCommit && round != rs.Round {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run step
|
// Run action
|
||||||
if action == RoundActionPropose && rs.Step == RoundStepStart {
|
switch action {
|
||||||
|
case RoundActionPropose:
|
||||||
|
if rs.Step != RoundStepStart {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
conR.conS.RunActionPropose(rs.Height, rs.Round)
|
conR.conS.RunActionPropose(rs.Height, rs.Round)
|
||||||
broadcastNewRoundStep(RoundStepPropose)
|
broadcastNewRoundStep(RoundStepPropose)
|
||||||
} else if action == RoundActionPrevote && rs.Step <= RoundStepPropose {
|
scheduleNextAction()
|
||||||
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
case RoundActionPrevote:
|
||||||
|
if rs.Step >= RoundStepPrevote {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
hash := conR.conS.RunActionPrevote(rs.Height, rs.Round)
|
hash := conR.conS.RunActionPrevote(rs.Height, rs.Round)
|
||||||
broadcastNewRoundStep(RoundStepPrevote)
|
broadcastNewRoundStep(RoundStepPrevote)
|
||||||
conR.signAndBroadcastVote(rs, &Vote{
|
conR.signAndBroadcastVote(rs, &Vote{
|
||||||
@ -354,7 +383,13 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
Type: VoteTypePrevote,
|
Type: VoteTypePrevote,
|
||||||
BlockHash: hash,
|
BlockHash: hash,
|
||||||
})
|
})
|
||||||
} else if action == RoundActionPrecommit && rs.Step <= RoundStepPrevote {
|
scheduleNextAction()
|
||||||
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
case RoundActionPrecommit:
|
||||||
|
if rs.Step >= RoundStepPrecommit {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
hash := conR.conS.RunActionPrecommit(rs.Height, rs.Round)
|
hash := conR.conS.RunActionPrecommit(rs.Height, rs.Round)
|
||||||
broadcastNewRoundStep(RoundStepPrecommit)
|
broadcastNewRoundStep(RoundStepPrecommit)
|
||||||
if len(hash) > 0 {
|
if len(hash) > 0 {
|
||||||
@ -365,8 +400,23 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
BlockHash: hash,
|
BlockHash: hash,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if action == RoundActionCommit && rs.Step <= RoundStepPrecommit {
|
scheduleNextAction()
|
||||||
hash := conR.conS.RunActionCommit(rs.Height, rs.Round)
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
case RoundActionNextRound:
|
||||||
|
if rs.Step >= RoundStepCommit {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
|
conR.conS.SetupRound(rs.Round + 1)
|
||||||
|
scheduleNextAction()
|
||||||
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
case RoundActionCommit:
|
||||||
|
if rs.Step >= RoundStepCommit {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
|
// NOTE: Duplicated in RoundActionCommitWait.
|
||||||
|
hash := conR.conS.RunActionCommit(rs.Height)
|
||||||
if len(hash) > 0 {
|
if len(hash) > 0 {
|
||||||
broadcastNewRoundStep(RoundStepCommit)
|
broadcastNewRoundStep(RoundStepCommit)
|
||||||
conR.signAndBroadcastVote(rs, &Vote{
|
conR.signAndBroadcastVote(rs, &Vote{
|
||||||
@ -376,17 +426,51 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
|
|||||||
BlockHash: hash,
|
BlockHash: hash,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
conR.conS.SetupRound(rs.Round + 1)
|
panic("This shouldn't happen")
|
||||||
}
|
}
|
||||||
} else if action == RoundActionFinalize && rs.Step == RoundStepCommit {
|
// do not schedule next action.
|
||||||
conR.conS.RunActionFinalize(rs.Height, rs.Round)
|
continue ACTION_LOOP
|
||||||
// Height has been incremented, step is now RoundStepStart.
|
|
||||||
|
case RoundActionCommitWait:
|
||||||
|
if rs.Step >= RoundStepCommitWait {
|
||||||
|
continue ACTION_LOOP
|
||||||
|
}
|
||||||
|
// First we must commit.
|
||||||
|
if rs.Step < RoundStepCommit {
|
||||||
|
// NOTE: Duplicated in RoundActionCommit.
|
||||||
|
hash := conR.conS.RunActionCommit(rs.Height)
|
||||||
|
if len(hash) > 0 {
|
||||||
|
broadcastNewRoundStep(RoundStepCommit)
|
||||||
|
conR.signAndBroadcastVote(rs, &Vote{
|
||||||
|
Height: rs.Height,
|
||||||
|
Round: rs.Round,
|
||||||
|
Type: VoteTypeCommit,
|
||||||
|
BlockHash: hash,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
// This shouldn't happen now, but if an external source pushes
|
panic("This shouldn't happen")
|
||||||
// to conR.doActionCh, we might just want to continue here.
|
|
||||||
panic("Shouldn't happen")
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// Now wait for more commit votes.
|
||||||
|
conR.conS.RunActionCommitWait(rs.Height)
|
||||||
scheduleNextAction()
|
scheduleNextAction()
|
||||||
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
case RoundActionFinalize:
|
||||||
|
if rs.Step != RoundStepCommitWait {
|
||||||
|
panic("This shouldn't happen")
|
||||||
|
}
|
||||||
|
conR.conS.RunActionFinalize(rs.Height)
|
||||||
|
// Height has been incremented, step is now RoundStepStart.
|
||||||
|
scheduleNextAction()
|
||||||
|
continue ACTION_LOOP
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic("Unknown action")
|
||||||
|
}
|
||||||
|
|
||||||
|
// For clarity, ensure that all switch cases call "continue"
|
||||||
|
panic("Should not happen.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,6 +488,24 @@ OUTER_LOOP:
|
|||||||
rs := conR.conS.GetRoundState()
|
rs := conR.conS.GetRoundState()
|
||||||
prs := ps.GetRoundState()
|
prs := ps.GetRoundState()
|
||||||
|
|
||||||
|
// If ProposalBlockHash matches, send parts?
|
||||||
|
// NOTE: if we or peer is at RoundStepCommit*, the round
|
||||||
|
// won't necessarily match, but that's OK.
|
||||||
|
if rs.ProposalBlock.HashesTo(prs.ProposalBlockHash) {
|
||||||
|
if index, ok := rs.ProposalBlockPartSet.BitArray().Sub(
|
||||||
|
prs.ProposalBlockBitArray).PickRandom(); ok {
|
||||||
|
msg := &PartMessage{
|
||||||
|
Height: rs.Height,
|
||||||
|
Round: rs.Round,
|
||||||
|
Type: partTypeProposalBlock,
|
||||||
|
Part: rs.ProposalBlockPartSet.GetPart(uint16(index)),
|
||||||
|
}
|
||||||
|
peer.Send(DataCh, msg)
|
||||||
|
ps.SetHasProposalBlockPart(rs.Height, rs.Round, uint16(index))
|
||||||
|
continue OUTER_LOOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// If height and round doesn't match, sleep.
|
// If height and round doesn't match, sleep.
|
||||||
if rs.Height != prs.Height || rs.Round != prs.Round {
|
if rs.Height != prs.Height || rs.Round != prs.Round {
|
||||||
time.Sleep(peerGossipSleepDuration)
|
time.Sleep(peerGossipSleepDuration)
|
||||||
@ -418,20 +520,6 @@ OUTER_LOOP:
|
|||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send proposal block part?
|
|
||||||
if index, ok := rs.ProposalBlockPartSet.BitArray().Sub(
|
|
||||||
prs.ProposalBlockBitArray).PickRandom(); ok {
|
|
||||||
msg := &PartMessage{
|
|
||||||
Height: rs.Height,
|
|
||||||
Round: rs.Round,
|
|
||||||
Type: partTypeProposalBlock,
|
|
||||||
Part: rs.ProposalBlockPartSet.GetPart(uint16(index)),
|
|
||||||
}
|
|
||||||
peer.Send(DataCh, msg)
|
|
||||||
ps.SetHasProposalBlockPart(rs.Height, rs.Round, uint16(index))
|
|
||||||
continue OUTER_LOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send proposal POL part?
|
// Send proposal POL part?
|
||||||
if index, ok := rs.ProposalPOLPartSet.BitArray().Sub(
|
if index, ok := rs.ProposalPOLPartSet.BitArray().Sub(
|
||||||
prs.ProposalPOLBitArray).PickRandom(); ok {
|
prs.ProposalPOLBitArray).PickRandom(); ok {
|
||||||
@ -481,6 +569,10 @@ OUTER_LOOP:
|
|||||||
msg := p2p.TypedMessage{msgTypeVote, vote}
|
msg := p2p.TypedMessage{msgTypeVote, vote}
|
||||||
peer.Send(VoteCh, msg)
|
peer.Send(VoteCh, msg)
|
||||||
ps.SetHasVote(rs.Height, rs.Round, VoteTypePrevote, uint32(index))
|
ps.SetHasVote(rs.Height, rs.Round, VoteTypePrevote, uint32(index))
|
||||||
|
if vote.Type == VoteTypeCommit {
|
||||||
|
ps.SetHasVote(rs.Height, rs.Round, VoteTypePrecommit, uint32(index))
|
||||||
|
ps.SetHasVote(rs.Height, rs.Round, VoteTypeCommit, uint32(index))
|
||||||
|
}
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
} else {
|
} else {
|
||||||
log.Error("index is not a valid validator index")
|
log.Error("index is not a valid validator index")
|
||||||
@ -498,6 +590,9 @@ OUTER_LOOP:
|
|||||||
msg := p2p.TypedMessage{msgTypeVote, vote}
|
msg := p2p.TypedMessage{msgTypeVote, vote}
|
||||||
peer.Send(VoteCh, msg)
|
peer.Send(VoteCh, msg)
|
||||||
ps.SetHasVote(rs.Height, rs.Round, VoteTypePrecommit, uint32(index))
|
ps.SetHasVote(rs.Height, rs.Round, VoteTypePrecommit, uint32(index))
|
||||||
|
if vote.Type == VoteTypeCommit {
|
||||||
|
ps.SetHasVote(rs.Height, rs.Round, VoteTypeCommit, uint32(index))
|
||||||
|
}
|
||||||
continue OUTER_LOOP
|
continue OUTER_LOOP
|
||||||
} else {
|
} else {
|
||||||
log.Error("index is not a valid validator index")
|
log.Error("index is not a valid validator index")
|
||||||
|
@ -23,6 +23,7 @@ const (
|
|||||||
RoundStepPrevote = RoundStep(0x02) // Did prevote, gossip prevotes.
|
RoundStepPrevote = RoundStep(0x02) // Did prevote, gossip prevotes.
|
||||||
RoundStepPrecommit = RoundStep(0x03) // Did precommit, gossip precommits.
|
RoundStepPrecommit = RoundStep(0x03) // Did precommit, gossip precommits.
|
||||||
RoundStepCommit = RoundStep(0x04) // Did commit, gossip commits.
|
RoundStepCommit = RoundStep(0x04) // Did commit, gossip commits.
|
||||||
|
RoundStepCommitWait = RoundStep(0x05) // Found +2/3 commits, wait more.
|
||||||
|
|
||||||
// If a block could not be committed at a given round,
|
// If a block could not be committed at a given round,
|
||||||
// we progress to the next round, skipping RoundStepCommit.
|
// we progress to the next round, skipping RoundStepCommit.
|
||||||
@ -30,12 +31,15 @@ const (
|
|||||||
// If a block was committed, we goto RoundStepCommit,
|
// If a block was committed, we goto RoundStepCommit,
|
||||||
// then wait "finalizeDuration" to gather more commits,
|
// then wait "finalizeDuration" to gather more commits,
|
||||||
// then we progress to the next height at round 0.
|
// then we progress to the next height at round 0.
|
||||||
|
// TODO: document how RoundStepCommit transcends all rounds.
|
||||||
|
|
||||||
RoundActionPropose = RoundActionType(0x00) // Goto RoundStepPropose
|
RoundActionPropose = RoundActionType(0x00) // Goto RoundStepPropose
|
||||||
RoundActionPrevote = RoundActionType(0x01) // Goto RoundStepPrevote
|
RoundActionPrevote = RoundActionType(0x01) // Goto RoundStepPrevote
|
||||||
RoundActionPrecommit = RoundActionType(0x02) // Goto RoundStepPrecommit
|
RoundActionPrecommit = RoundActionType(0x02) // Goto RoundStepPrecommit
|
||||||
RoundActionCommit = RoundActionType(0x03) // Goto RoundStepCommit or RoundStepStart next round
|
RoundActionNextRound = RoundActionType(0x04) // Goto next round RoundStepStart
|
||||||
RoundActionFinalize = RoundActionType(0x04) // Goto RoundStepStart next height
|
RoundActionCommit = RoundActionType(0x05) // Goto RoundStepCommit or RoundStepStart next round
|
||||||
|
RoundActionCommitWait = RoundActionType(0x06) // Goto RoundStepCommitWait
|
||||||
|
RoundActionFinalize = RoundActionType(0x07) // Goto RoundStepStart next height
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -50,6 +54,7 @@ type RoundState struct {
|
|||||||
Round uint16
|
Round uint16
|
||||||
Step RoundStep
|
Step RoundStep
|
||||||
StartTime time.Time
|
StartTime time.Time
|
||||||
|
CommitTime time.Time // Time when +2/3 commits were found
|
||||||
Validators *state.ValidatorSet
|
Validators *state.ValidatorSet
|
||||||
Proposal *Proposal
|
Proposal *Proposal
|
||||||
ProposalBlock *Block
|
ProposalBlock *Block
|
||||||
@ -57,7 +62,8 @@ type RoundState struct {
|
|||||||
ProposalPOL *POL
|
ProposalPOL *POL
|
||||||
ProposalPOLPartSet *PartSet
|
ProposalPOLPartSet *PartSet
|
||||||
LockedBlock *Block
|
LockedBlock *Block
|
||||||
LockedPOL *POL
|
LockedBlockPartSet *PartSet
|
||||||
|
LockedPOL *POL // Rarely needed, so no LockedPOLPartSet.
|
||||||
Prevotes *VoteSet
|
Prevotes *VoteSet
|
||||||
Precommits *VoteSet
|
Precommits *VoteSet
|
||||||
Commits *VoteSet
|
Commits *VoteSet
|
||||||
@ -77,11 +83,12 @@ func (rs *RoundState) StringWithIndent(indent string) string {
|
|||||||
return fmt.Sprintf(`RoundState{
|
return fmt.Sprintf(`RoundState{
|
||||||
%s H:%v R:%v S:%v
|
%s H:%v R:%v S:%v
|
||||||
%s StartTime: %v
|
%s StartTime: %v
|
||||||
|
%s CommitTime: %v
|
||||||
%s Validators: %v
|
%s Validators: %v
|
||||||
%s Proposal: %v
|
%s Proposal: %v
|
||||||
%s ProposalBlock: %v %v
|
%s ProposalBlock: %v %v
|
||||||
%s ProposalPOL: %v %v
|
%s ProposalPOL: %v %v
|
||||||
%s LockedBlock: %v
|
%s LockedBlock: %v %v
|
||||||
%s LockedPOL: %v
|
%s LockedPOL: %v
|
||||||
%s Prevotes: %v
|
%s Prevotes: %v
|
||||||
%s Precommits: %v
|
%s Precommits: %v
|
||||||
@ -90,11 +97,12 @@ func (rs *RoundState) StringWithIndent(indent string) string {
|
|||||||
%s}`,
|
%s}`,
|
||||||
indent, rs.Height, rs.Round, rs.Step,
|
indent, rs.Height, rs.Round, rs.Step,
|
||||||
indent, rs.StartTime,
|
indent, rs.StartTime,
|
||||||
|
indent, rs.CommitTime,
|
||||||
indent, rs.Validators.StringWithIndent(indent+" "),
|
indent, rs.Validators.StringWithIndent(indent+" "),
|
||||||
indent, rs.Proposal,
|
indent, rs.Proposal,
|
||||||
indent, rs.ProposalBlockPartSet.Description(), rs.ProposalBlock.Description(),
|
indent, rs.ProposalBlockPartSet.Description(), rs.ProposalBlock.Description(),
|
||||||
indent, rs.ProposalPOLPartSet.Description(), rs.ProposalPOL.Description(),
|
indent, rs.ProposalPOLPartSet.Description(), rs.ProposalPOL.Description(),
|
||||||
indent, rs.LockedBlock.Description(),
|
indent, rs.LockedBlockPartSet.Description(), rs.LockedBlock.Description(),
|
||||||
indent, rs.LockedPOL.Description(),
|
indent, rs.LockedPOL.Description(),
|
||||||
indent, rs.Prevotes.StringWithIndent(indent+" "),
|
indent, rs.Prevotes.StringWithIndent(indent+" "),
|
||||||
indent, rs.Precommits.StringWithIndent(indent+" "),
|
indent, rs.Precommits.StringWithIndent(indent+" "),
|
||||||
@ -146,7 +154,12 @@ func (cs *ConsensusState) updateToState(state *state.State) {
|
|||||||
cs.Height = height
|
cs.Height = height
|
||||||
cs.Round = 0
|
cs.Round = 0
|
||||||
cs.Step = RoundStepStart
|
cs.Step = RoundStepStart
|
||||||
cs.StartTime = state.CommitTime.Add(finalizeDuration)
|
if cs.CommitTime.IsZero() {
|
||||||
|
cs.StartTime = state.BlockTime.Add(finalizeDuration)
|
||||||
|
} else {
|
||||||
|
cs.StartTime = cs.CommitTime.Add(finalizeDuration)
|
||||||
|
}
|
||||||
|
cs.CommitTime = time.Time{}
|
||||||
cs.Validators = validators
|
cs.Validators = validators
|
||||||
cs.Proposal = nil
|
cs.Proposal = nil
|
||||||
cs.ProposalBlock = nil
|
cs.ProposalBlock = nil
|
||||||
@ -154,6 +167,7 @@ func (cs *ConsensusState) updateToState(state *state.State) {
|
|||||||
cs.ProposalPOL = nil
|
cs.ProposalPOL = nil
|
||||||
cs.ProposalPOLPartSet = nil
|
cs.ProposalPOLPartSet = nil
|
||||||
cs.LockedBlock = nil
|
cs.LockedBlock = nil
|
||||||
|
cs.LockedBlockPartSet = nil
|
||||||
cs.LockedPOL = nil
|
cs.LockedPOL = nil
|
||||||
cs.Prevotes = NewVoteSet(height, 0, VoteTypePrevote, validators)
|
cs.Prevotes = NewVoteSet(height, 0, VoteTypePrevote, validators)
|
||||||
cs.Precommits = NewVoteSet(height, 0, VoteTypePrecommit, validators)
|
cs.Precommits = NewVoteSet(height, 0, VoteTypePrecommit, validators)
|
||||||
@ -254,6 +268,7 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) {
|
|||||||
if cs.LockedBlock != nil {
|
if cs.LockedBlock != nil {
|
||||||
// If we're locked onto a block, just choose that.
|
// If we're locked onto a block, just choose that.
|
||||||
block = cs.LockedBlock
|
block = cs.LockedBlock
|
||||||
|
blockPartSet = cs.LockedBlockPartSet
|
||||||
pol = cs.LockedPOL
|
pol = cs.LockedPOL
|
||||||
} else {
|
} else {
|
||||||
var validation Validation
|
var validation Validation
|
||||||
@ -284,14 +299,12 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) {
|
|||||||
Txs: txs,
|
Txs: txs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
blockPartSet = NewPartSetFromData(BinaryBytes(block))
|
||||||
pol = cs.LockedPOL // If exists, is a PoUnlock.
|
pol = cs.LockedPOL // If exists, is a PoUnlock.
|
||||||
}
|
}
|
||||||
|
|
||||||
blockPartSet = NewPartSetFromData(BinaryBytes(block))
|
|
||||||
if pol != nil {
|
if pol != nil {
|
||||||
polPartSet = NewPartSetFromData(BinaryBytes(pol))
|
polPartSet = NewPartSetFromData(BinaryBytes(pol))
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make proposal
|
// Make proposal
|
||||||
@ -423,6 +436,7 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte
|
|||||||
if len(hash) == 0 {
|
if len(hash) == 0 {
|
||||||
// +2/3 prevoted nil. Just unlock.
|
// +2/3 prevoted nil. Just unlock.
|
||||||
cs.LockedBlock = nil
|
cs.LockedBlock = nil
|
||||||
|
cs.LockedBlockPartSet = nil
|
||||||
return nil
|
return nil
|
||||||
} else if cs.ProposalBlock.HashesTo(hash) {
|
} else if cs.ProposalBlock.HashesTo(hash) {
|
||||||
// +2/3 prevoted for proposal block
|
// +2/3 prevoted for proposal block
|
||||||
@ -433,15 +447,16 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cs.LockedBlock = cs.ProposalBlock
|
cs.LockedBlock = cs.ProposalBlock
|
||||||
|
cs.LockedBlockPartSet = cs.ProposalBlockPartSet
|
||||||
return hash
|
return hash
|
||||||
} else if cs.LockedBlock.HashesTo(hash) {
|
} else if cs.LockedBlock.HashesTo(hash) {
|
||||||
// +2/3 prevoted for already locked block
|
// +2/3 prevoted for already locked block
|
||||||
// cs.LockedBlock = cs.LockedBlock
|
|
||||||
return hash
|
return hash
|
||||||
} else {
|
} else {
|
||||||
// We don't have the block that hashes to hash.
|
// We don't have the block that hashes to hash.
|
||||||
// Unlock if we're locked.
|
// Unlock if we're locked.
|
||||||
cs.LockedBlock = nil
|
cs.LockedBlock = nil
|
||||||
|
cs.LockedBlockPartSet = nil
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -454,15 +469,15 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte
|
|||||||
// and returns the committed block.
|
// and returns the committed block.
|
||||||
// Commit is not finalized until FinalizeCommit() is called.
|
// Commit is not finalized until FinalizeCommit() is called.
|
||||||
// This allows us to stay at this height and gather more commits.
|
// This allows us to stay at this height and gather more commits.
|
||||||
func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte {
|
func (cs *ConsensusState) RunActionCommit(height uint32) []byte {
|
||||||
cs.mtx.Lock()
|
cs.mtx.Lock()
|
||||||
defer cs.mtx.Unlock()
|
defer cs.mtx.Unlock()
|
||||||
if cs.Height != height || cs.Round != round {
|
if cs.Height != height {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
cs.Step = RoundStepCommit
|
cs.Step = RoundStepCommit
|
||||||
|
|
||||||
if hash, commitTime, ok := cs.Precommits.TwoThirdsMajority(); ok {
|
if hash, _, ok := cs.Precommits.TwoThirdsMajority(); ok {
|
||||||
|
|
||||||
// There are some strange cases that shouldn't happen
|
// There are some strange cases that shouldn't happen
|
||||||
// (unless voters are duplicitous).
|
// (unless voters are duplicitous).
|
||||||
@ -473,10 +488,13 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte {
|
|||||||
// TODO: Identify these strange cases.
|
// TODO: Identify these strange cases.
|
||||||
|
|
||||||
var block *Block
|
var block *Block
|
||||||
|
var blockPartSet *PartSet
|
||||||
if cs.LockedBlock.HashesTo(hash) {
|
if cs.LockedBlock.HashesTo(hash) {
|
||||||
block = cs.LockedBlock
|
block = cs.LockedBlock
|
||||||
|
blockPartSet = cs.LockedBlockPartSet
|
||||||
} else if cs.ProposalBlock.HashesTo(hash) {
|
} else if cs.ProposalBlock.HashesTo(hash) {
|
||||||
block = cs.ProposalBlock
|
block = cs.ProposalBlock
|
||||||
|
blockPartSet = cs.ProposalBlockPartSet
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -487,11 +505,17 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep block in cs.Proposal*
|
||||||
|
if !cs.ProposalBlock.HashesTo(hash) {
|
||||||
|
cs.ProposalBlock = block
|
||||||
|
cs.ProposalBlockPartSet = blockPartSet
|
||||||
|
}
|
||||||
|
|
||||||
// Save to blockStore
|
// Save to blockStore
|
||||||
cs.blockStore.SaveBlock(block)
|
cs.blockStore.SaveBlock(block)
|
||||||
|
|
||||||
// Save the state
|
// Save the state
|
||||||
cs.stagedState.Save(commitTime)
|
cs.stagedState.Save()
|
||||||
|
|
||||||
// Update mempool.
|
// Update mempool.
|
||||||
cs.mempool.ResetForBlockAndState(block, cs.stagedState)
|
cs.mempool.ResetForBlockAndState(block, cs.stagedState)
|
||||||
@ -502,12 +526,26 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// After TryCommit(), if successful, must call this in order to
|
func (cs *ConsensusState) RunActionCommitWait(height uint32) {
|
||||||
// update the RoundState.
|
|
||||||
func (cs *ConsensusState) RunActionFinalize(height uint32, round uint16) {
|
|
||||||
cs.mtx.Lock()
|
cs.mtx.Lock()
|
||||||
defer cs.mtx.Unlock()
|
defer cs.mtx.Unlock()
|
||||||
if cs.Height != height || cs.Round != round {
|
if cs.Height != height {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cs.Step = RoundStepCommitWait
|
||||||
|
|
||||||
|
if _, commitTime, ok := cs.Commits.TwoThirdsMajority(); ok {
|
||||||
|
// Remember the commitTime.
|
||||||
|
cs.CommitTime = commitTime
|
||||||
|
} else {
|
||||||
|
panic("RunActionCommitWait() expects +2/3 commits")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs *ConsensusState) RunActionFinalize(height uint32) {
|
||||||
|
cs.mtx.Lock()
|
||||||
|
defer cs.mtx.Unlock()
|
||||||
|
if cs.Height != height {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ func randAccountDetail(id uint64, status byte) (*state.AccountDetail, *state.Pri
|
|||||||
return &state.AccountDetail{
|
return &state.AccountDetail{
|
||||||
Account: account,
|
Account: account,
|
||||||
Sequence: RandUInt(),
|
Sequence: RandUInt(),
|
||||||
Balance: RandUInt64() + 1000, // At least 1000.
|
Balance: 1000,
|
||||||
Status: status,
|
Status: status,
|
||||||
}, privAccount
|
}, privAccount
|
||||||
}
|
}
|
||||||
@ -38,7 +38,7 @@ func randGenesisState(numAccounts int, numValidators int) (*state.State, []*stat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s0 := state.GenesisState(db, time.Now(), accountDetails)
|
s0 := state.GenesisState(db, time.Now(), accountDetails)
|
||||||
s0.Save(time.Now())
|
s0.Save()
|
||||||
return s0, privAccounts
|
return s0, privAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ func checkRoundState(t *testing.T, cs *ConsensusState,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunActionPrecommit(t *testing.T) {
|
func TestRunActionPrecommitCommitFinalize(t *testing.T) {
|
||||||
cs, privAccounts := makeConsensusState()
|
cs, privAccounts := makeConsensusState()
|
||||||
priv := NewPrivValidator(privAccounts[0], db_.NewMemDB())
|
priv := NewPrivValidator(privAccounts[0], db_.NewMemDB())
|
||||||
cs.SetPrivValidator(priv)
|
cs.SetPrivValidator(priv)
|
||||||
@ -175,7 +175,7 @@ func TestRunActionPrecommit(t *testing.T) {
|
|||||||
for i := 0; i < 7; i++ {
|
for i := 0; i < 7; i++ {
|
||||||
vote := &Vote{
|
vote := &Vote{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
Round: uint16(i),
|
Round: 0,
|
||||||
Type: VoteTypePrevote,
|
Type: VoteTypePrevote,
|
||||||
BlockHash: cs.ProposalBlock.Hash(),
|
BlockHash: cs.ProposalBlock.Hash(),
|
||||||
}
|
}
|
||||||
@ -191,15 +191,11 @@ func TestRunActionPrecommit(t *testing.T) {
|
|||||||
checkRoundState(t, cs, 1, 0, RoundStepPrecommit)
|
checkRoundState(t, cs, 1, 0, RoundStepPrecommit)
|
||||||
|
|
||||||
// Test RunActionCommit failures:
|
// Test RunActionCommit failures:
|
||||||
blockHash = cs.RunActionCommit(1, 1)
|
blockHash = cs.RunActionCommit(2)
|
||||||
if blockHash != nil {
|
|
||||||
t.Errorf("RunActionCommit should fail for wrong round")
|
|
||||||
}
|
|
||||||
blockHash = cs.RunActionCommit(2, 0)
|
|
||||||
if blockHash != nil {
|
if blockHash != nil {
|
||||||
t.Errorf("RunActionCommit should fail for wrong height")
|
t.Errorf("RunActionCommit should fail for wrong height")
|
||||||
}
|
}
|
||||||
blockHash = cs.RunActionCommit(1, 0)
|
blockHash = cs.RunActionCommit(1)
|
||||||
if blockHash != nil {
|
if blockHash != nil {
|
||||||
t.Errorf("RunActionCommit should fail, not enough commits")
|
t.Errorf("RunActionCommit should fail, not enough commits")
|
||||||
}
|
}
|
||||||
@ -208,7 +204,7 @@ func TestRunActionPrecommit(t *testing.T) {
|
|||||||
for i := 0; i < 7; i++ {
|
for i := 0; i < 7; i++ {
|
||||||
vote := &Vote{
|
vote := &Vote{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
Round: uint16(i),
|
Round: 0,
|
||||||
Type: VoteTypePrecommit,
|
Type: VoteTypePrecommit,
|
||||||
BlockHash: cs.ProposalBlock.Hash(),
|
BlockHash: cs.ProposalBlock.Hash(),
|
||||||
}
|
}
|
||||||
@ -217,10 +213,37 @@ func TestRunActionPrecommit(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test RunActionCommit success:
|
// Test RunActionCommit success:
|
||||||
blockHash = cs.RunActionCommit(1, 0)
|
blockHash = cs.RunActionCommit(1)
|
||||||
if len(blockHash) == 0 {
|
if len(blockHash) == 0 {
|
||||||
t.Errorf("RunActionCommit should have succeeded")
|
t.Errorf("RunActionCommit should have succeeded")
|
||||||
}
|
}
|
||||||
checkRoundState(t, cs, 1, 0, RoundStepCommit)
|
checkRoundState(t, cs, 1, 0, RoundStepCommit)
|
||||||
|
|
||||||
|
// cs.CommitTime should still be zero
|
||||||
|
if !cs.CommitTime.IsZero() {
|
||||||
|
t.Errorf("Expected CommitTime to yet be zero")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add at least +2/3 commits.
|
||||||
|
for i := 0; i < 7; i++ {
|
||||||
|
vote := &Vote{
|
||||||
|
Height: 1,
|
||||||
|
Round: uint16(i), // Doesn't matter what round
|
||||||
|
Type: VoteTypeCommit,
|
||||||
|
BlockHash: cs.ProposalBlock.Hash(),
|
||||||
|
}
|
||||||
|
privAccounts[i].Sign(vote)
|
||||||
|
cs.AddVote(vote)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test RunActionCommitWait:
|
||||||
|
cs.RunActionCommitWait(1)
|
||||||
|
if cs.CommitTime.IsZero() {
|
||||||
|
t.Errorf("Expected CommitTime to have been set")
|
||||||
|
}
|
||||||
|
checkRoundState(t, cs, 1, 0, RoundStepCommitWait)
|
||||||
|
|
||||||
|
// Test RunActionFinalize:
|
||||||
|
cs.RunActionFinalize(1)
|
||||||
|
checkRoundState(t, cs, 2, 0, RoundStepStart)
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
// Note that there three kinds of votes: prevotes, precommits, and commits.
|
// Note that there three kinds of votes: prevotes, precommits, and commits.
|
||||||
// A commit of prior rounds can be added added in lieu of votes/precommits.
|
// A commit of prior rounds can be added added in lieu of votes/precommits.
|
||||||
// TODO: test majority calculations etc.
|
// TODO: test majority calculations etc.
|
||||||
|
// NOTE: assumes that the sum total of voting power does not exceed MaxUInt64.
|
||||||
type VoteSet struct {
|
type VoteSet struct {
|
||||||
height uint32
|
height uint32
|
||||||
round uint16
|
round uint16
|
||||||
|
@ -45,7 +45,7 @@ type State struct {
|
|||||||
DB db_.DB
|
DB db_.DB
|
||||||
Height uint32 // Last known block height
|
Height uint32 // Last known block height
|
||||||
BlockHash []byte // Last known block hash
|
BlockHash []byte // Last known block hash
|
||||||
CommitTime time.Time
|
BlockTime time.Time // LastKnown block time
|
||||||
BondedValidators *ValidatorSet
|
BondedValidators *ValidatorSet
|
||||||
UnbondingValidators *ValidatorSet
|
UnbondingValidators *ValidatorSet
|
||||||
accountDetails merkle.Tree // Shouldn't be accessed directly.
|
accountDetails merkle.Tree // Shouldn't be accessed directly.
|
||||||
@ -77,7 +77,7 @@ func GenesisState(db db_.DB, genesisTime time.Time, accDets []*AccountDetail) *S
|
|||||||
DB: db,
|
DB: db,
|
||||||
Height: 0,
|
Height: 0,
|
||||||
BlockHash: nil,
|
BlockHash: nil,
|
||||||
CommitTime: genesisTime,
|
BlockTime: genesisTime,
|
||||||
BondedValidators: NewValidatorSet(validators),
|
BondedValidators: NewValidatorSet(validators),
|
||||||
UnbondingValidators: NewValidatorSet(nil),
|
UnbondingValidators: NewValidatorSet(nil),
|
||||||
accountDetails: accountDetails,
|
accountDetails: accountDetails,
|
||||||
@ -94,8 +94,8 @@ func LoadState(db db_.DB) *State {
|
|||||||
var n int64
|
var n int64
|
||||||
var err error
|
var err error
|
||||||
s.Height = ReadUInt32(reader, &n, &err)
|
s.Height = ReadUInt32(reader, &n, &err)
|
||||||
s.CommitTime = ReadTime(reader, &n, &err)
|
|
||||||
s.BlockHash = ReadByteSlice(reader, &n, &err)
|
s.BlockHash = ReadByteSlice(reader, &n, &err)
|
||||||
|
s.BlockTime = ReadTime(reader, &n, &err)
|
||||||
s.BondedValidators = ReadValidatorSet(reader, &n, &err)
|
s.BondedValidators = ReadValidatorSet(reader, &n, &err)
|
||||||
s.UnbondingValidators = ReadValidatorSet(reader, &n, &err)
|
s.UnbondingValidators = ReadValidatorSet(reader, &n, &err)
|
||||||
accountDetailsHash := ReadByteSlice(reader, &n, &err)
|
accountDetailsHash := ReadByteSlice(reader, &n, &err)
|
||||||
@ -110,17 +110,14 @@ func LoadState(db db_.DB) *State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save this state into the db.
|
// Save this state into the db.
|
||||||
// For convenience, the commitTime (required by ConsensusAgent)
|
func (s *State) Save() {
|
||||||
// is saved here.
|
|
||||||
func (s *State) Save(commitTime time.Time) {
|
|
||||||
s.CommitTime = commitTime
|
|
||||||
s.accountDetails.Save()
|
s.accountDetails.Save()
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
var n int64
|
var n int64
|
||||||
var err error
|
var err error
|
||||||
WriteUInt32(&buf, s.Height, &n, &err)
|
WriteUInt32(&buf, s.Height, &n, &err)
|
||||||
WriteTime(&buf, commitTime, &n, &err)
|
|
||||||
WriteByteSlice(&buf, s.BlockHash, &n, &err)
|
WriteByteSlice(&buf, s.BlockHash, &n, &err)
|
||||||
|
WriteTime(&buf, s.BlockTime, &n, &err)
|
||||||
WriteBinary(&buf, s.BondedValidators, &n, &err)
|
WriteBinary(&buf, s.BondedValidators, &n, &err)
|
||||||
WriteBinary(&buf, s.UnbondingValidators, &n, &err)
|
WriteBinary(&buf, s.UnbondingValidators, &n, &err)
|
||||||
WriteByteSlice(&buf, s.accountDetails.Hash(), &n, &err)
|
WriteByteSlice(&buf, s.accountDetails.Hash(), &n, &err)
|
||||||
@ -134,8 +131,8 @@ func (s *State) Copy() *State {
|
|||||||
return &State{
|
return &State{
|
||||||
DB: s.DB,
|
DB: s.DB,
|
||||||
Height: s.Height,
|
Height: s.Height,
|
||||||
CommitTime: s.CommitTime,
|
|
||||||
BlockHash: s.BlockHash,
|
BlockHash: s.BlockHash,
|
||||||
|
BlockTime: s.BlockTime,
|
||||||
BondedValidators: s.BondedValidators.Copy(),
|
BondedValidators: s.BondedValidators.Copy(),
|
||||||
UnbondingValidators: s.UnbondingValidators.Copy(),
|
UnbondingValidators: s.UnbondingValidators.Copy(),
|
||||||
accountDetails: s.accountDetails.Copy(),
|
accountDetails: s.accountDetails.Copy(),
|
||||||
@ -397,6 +394,7 @@ func (s *State) AppendBlock(b *Block, checkStateHash bool) error {
|
|||||||
|
|
||||||
s.Height = b.Height
|
s.Height = b.Height
|
||||||
s.BlockHash = b.Hash()
|
s.BlockHash = b.Hash()
|
||||||
|
s.BlockTime = b.Time
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -418,7 +416,7 @@ func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Returns a hash that represents the state data,
|
// Returns a hash that represents the state data,
|
||||||
// excluding Height, BlockHash, and CommitTime.
|
// excluding Height, BlockHash.
|
||||||
func (s *State) Hash() []byte {
|
func (s *State) Hash() []byte {
|
||||||
hashables := []merkle.Hashable{
|
hashables := []merkle.Hashable{
|
||||||
s.BondedValidators,
|
s.BondedValidators,
|
||||||
|
@ -38,7 +38,7 @@ func randGenesisState(numAccounts int, numValidators int) (*State, []*PrivAccoun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
s0 := GenesisState(db, time.Now(), accountDetails)
|
s0 := GenesisState(db, time.Now(), accountDetails)
|
||||||
s0.Save(time.Now())
|
s0.Save()
|
||||||
return s0, privAccounts
|
return s0, privAccounts
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,8 +110,7 @@ func TestGenesisSaveLoad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save s0
|
// Save s0
|
||||||
commitTime := time.Now()
|
s0.Save()
|
||||||
s0.Save(commitTime)
|
|
||||||
|
|
||||||
// Sanity check s0
|
// Sanity check s0
|
||||||
//s0.DB.(*db_.MemDB).Print()
|
//s0.DB.(*db_.MemDB).Print()
|
||||||
@ -125,10 +124,6 @@ func TestGenesisSaveLoad(t *testing.T) {
|
|||||||
// Load s1
|
// Load s1
|
||||||
s1 := LoadState(s0.DB)
|
s1 := LoadState(s0.DB)
|
||||||
|
|
||||||
// Compare CommitTime
|
|
||||||
if !s0.CommitTime.Equal(s1.CommitTime) {
|
|
||||||
t.Error("CommitTime was not the same", s0.CommitTime, s1.CommitTime)
|
|
||||||
}
|
|
||||||
// Compare height & blockHash
|
// Compare height & blockHash
|
||||||
if s0.Height != s1.Height {
|
if s0.Height != s1.Height {
|
||||||
t.Error("Height mismatch")
|
t.Error("Height mismatch")
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Not goroutine-safe.
|
// Not goroutine-safe.
|
||||||
|
// TODO: consider validator Accum overflow?
|
||||||
type ValidatorSet struct {
|
type ValidatorSet struct {
|
||||||
validators merkle.Tree
|
validators merkle.Tree
|
||||||
proposer *Validator // Whoever has the highest Accum.
|
proposer *Validator // Whoever has the highest Accum.
|
||||||
|
Reference in New Issue
Block a user