From 89418ee7afb33758612fa2d5b2af65f9d3d5efa9 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Tue, 21 Oct 2014 18:30:03 -0700 Subject: [PATCH] Added CommitTimeWait step, the state machine model changed a bit. --- consensus/consensus.go | 165 +++++++++++++++++++++++++++++++--------- consensus/state.go | 90 +++++++++++++++------- consensus/state_test.go | 47 +++++++++--- consensus/vote_set.go | 1 + state/state.go | 22 +++--- state/state_test.go | 9 +-- state/validator_set.go | 1 + 7 files changed, 243 insertions(+), 92 deletions(-) diff --git a/consensus/consensus.go b/consensus/consensus.go index d3a66907..01ba2b49 100644 --- a/consensus/consensus.go +++ b/consensus/consensus.go @@ -253,6 +253,13 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } 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: @@ -304,10 +311,15 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { case RoundStepPrecommit: // Wake up when the round is over. 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: + panic("Should not happen: RoundStepCommit waits until +2/3 commits.") + case RoundStepCommitWait: // 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} default: panic("Should not happen") @@ -316,6 +328,11 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { 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 { roundAction := <-conR.doActionCh @@ -334,18 +351,30 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { conR.sw.Broadcast(StateCh, msg) } - if height != rs.Height || round != rs.Round { - // Action is not relevant - // This may happen if an external routine - // pushes an action to conR.doActionCh. - return + // Continue if action is not relevant + if height != rs.Height { + continue + } + // If action >= RoundActionCommit, the round doesn't matter. + if action < RoundActionCommit && round != rs.Round { + continue } - // Run step - if action == RoundActionPropose && rs.Step == RoundStepStart { + // Run action + switch action { + case RoundActionPropose: + if rs.Step != RoundStepStart { + continue ACTION_LOOP + } conR.conS.RunActionPropose(rs.Height, rs.Round) 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) broadcastNewRoundStep(RoundStepPrevote) conR.signAndBroadcastVote(rs, &Vote{ @@ -354,7 +383,13 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { Type: VoteTypePrevote, 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) broadcastNewRoundStep(RoundStepPrecommit) if len(hash) > 0 { @@ -365,8 +400,23 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { BlockHash: hash, }) } - } else if action == RoundActionCommit && rs.Step <= RoundStepPrecommit { - hash := conR.conS.RunActionCommit(rs.Height, rs.Round) + scheduleNextAction() + 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 { broadcastNewRoundStep(RoundStepCommit) conR.signAndBroadcastVote(rs, &Vote{ @@ -376,17 +426,51 @@ func (conR *ConsensusReactor) stepTransitionRoutine() { BlockHash: hash, }) } else { - conR.conS.SetupRound(rs.Round + 1) + panic("This shouldn't happen") } - } else if action == RoundActionFinalize && rs.Step == RoundStepCommit { - conR.conS.RunActionFinalize(rs.Height, rs.Round) + // do not schedule next action. + continue ACTION_LOOP + + 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 { + panic("This shouldn't happen") + } + } + // Now wait for more commit votes. + conR.conS.RunActionCommitWait(rs.Height) + 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. - } else { - // This shouldn't happen now, but if an external source pushes - // to conR.doActionCh, we might just want to continue here. - panic("Shouldn't happen") + scheduleNextAction() + continue ACTION_LOOP + + default: + panic("Unknown action") } - scheduleNextAction() + + // For clarity, ensure that all switch cases call "continue" + panic("Should not happen.") } } @@ -404,6 +488,24 @@ OUTER_LOOP: rs := conR.conS.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 rs.Height != prs.Height || rs.Round != prs.Round { time.Sleep(peerGossipSleepDuration) @@ -418,20 +520,6 @@ 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? if index, ok := rs.ProposalPOLPartSet.BitArray().Sub( prs.ProposalPOLBitArray).PickRandom(); ok { @@ -481,6 +569,10 @@ OUTER_LOOP: msg := p2p.TypedMessage{msgTypeVote, vote} peer.Send(VoteCh, msg) 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 } else { log.Error("index is not a valid validator index") @@ -498,6 +590,9 @@ OUTER_LOOP: msg := p2p.TypedMessage{msgTypeVote, vote} peer.Send(VoteCh, msg) 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 } else { log.Error("index is not a valid validator index") diff --git a/consensus/state.go b/consensus/state.go index ab2a1a9b..68688e98 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -18,11 +18,12 @@ type RoundStep uint8 type RoundActionType uint8 const ( - RoundStepStart = RoundStep(0x00) // Round started. - RoundStepPropose = RoundStep(0x01) // Did propose, gossip proposal. - RoundStepPrevote = RoundStep(0x02) // Did prevote, gossip prevotes. - RoundStepPrecommit = RoundStep(0x03) // Did precommit, gossip precommits. - RoundStepCommit = RoundStep(0x04) // Did commit, gossip commits. + RoundStepStart = RoundStep(0x00) // Round started. + RoundStepPropose = RoundStep(0x01) // Did propose, gossip proposal. + RoundStepPrevote = RoundStep(0x02) // Did prevote, gossip prevotes. + RoundStepPrecommit = RoundStep(0x03) // Did precommit, gossip precommits. + 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, // we progress to the next round, skipping RoundStepCommit. @@ -30,12 +31,15 @@ const ( // If a block was committed, we goto RoundStepCommit, // then wait "finalizeDuration" to gather more commits, // then we progress to the next height at round 0. + // TODO: document how RoundStepCommit transcends all rounds. - RoundActionPropose = RoundActionType(0x00) // Goto RoundStepPropose - RoundActionPrevote = RoundActionType(0x01) // Goto RoundStepPrevote - RoundActionPrecommit = RoundActionType(0x02) // Goto RoundStepPrecommit - RoundActionCommit = RoundActionType(0x03) // Goto RoundStepCommit or RoundStepStart next round - RoundActionFinalize = RoundActionType(0x04) // Goto RoundStepStart next height + RoundActionPropose = RoundActionType(0x00) // Goto RoundStepPropose + RoundActionPrevote = RoundActionType(0x01) // Goto RoundStepPrevote + RoundActionPrecommit = RoundActionType(0x02) // Goto RoundStepPrecommit + RoundActionNextRound = RoundActionType(0x04) // Goto next round RoundStepStart + RoundActionCommit = RoundActionType(0x05) // Goto RoundStepCommit or RoundStepStart next round + RoundActionCommitWait = RoundActionType(0x06) // Goto RoundStepCommitWait + RoundActionFinalize = RoundActionType(0x07) // Goto RoundStepStart next height ) var ( @@ -50,6 +54,7 @@ type RoundState struct { Round uint16 Step RoundStep StartTime time.Time + CommitTime time.Time // Time when +2/3 commits were found Validators *state.ValidatorSet Proposal *Proposal ProposalBlock *Block @@ -57,7 +62,8 @@ type RoundState struct { ProposalPOL *POL ProposalPOLPartSet *PartSet LockedBlock *Block - LockedPOL *POL + LockedBlockPartSet *PartSet + LockedPOL *POL // Rarely needed, so no LockedPOLPartSet. Prevotes *VoteSet Precommits *VoteSet Commits *VoteSet @@ -77,11 +83,12 @@ func (rs *RoundState) StringWithIndent(indent string) string { return fmt.Sprintf(`RoundState{ %s H:%v R:%v S:%v %s StartTime: %v +%s CommitTime: %v %s Validators: %v %s Proposal: %v %s ProposalBlock: %v %v %s ProposalPOL: %v %v -%s LockedBlock: %v +%s LockedBlock: %v %v %s LockedPOL: %v %s Prevotes: %v %s Precommits: %v @@ -90,11 +97,12 @@ func (rs *RoundState) StringWithIndent(indent string) string { %s}`, indent, rs.Height, rs.Round, rs.Step, indent, rs.StartTime, + indent, rs.CommitTime, indent, rs.Validators.StringWithIndent(indent+" "), indent, rs.Proposal, indent, rs.ProposalBlockPartSet.Description(), rs.ProposalBlock.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.Prevotes.StringWithIndent(indent+" "), indent, rs.Precommits.StringWithIndent(indent+" "), @@ -146,7 +154,12 @@ func (cs *ConsensusState) updateToState(state *state.State) { cs.Height = height cs.Round = 0 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.Proposal = nil cs.ProposalBlock = nil @@ -154,6 +167,7 @@ func (cs *ConsensusState) updateToState(state *state.State) { cs.ProposalPOL = nil cs.ProposalPOLPartSet = nil cs.LockedBlock = nil + cs.LockedBlockPartSet = nil cs.LockedPOL = nil cs.Prevotes = NewVoteSet(height, 0, VoteTypePrevote, 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 we're locked onto a block, just choose that. block = cs.LockedBlock + blockPartSet = cs.LockedBlockPartSet pol = cs.LockedPOL } else { var validation Validation @@ -284,14 +299,12 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) { Txs: txs, }, } + blockPartSet = NewPartSetFromData(BinaryBytes(block)) pol = cs.LockedPOL // If exists, is a PoUnlock. } - blockPartSet = NewPartSetFromData(BinaryBytes(block)) if pol != nil { polPartSet = NewPartSetFromData(BinaryBytes(pol)) - } else { - } // Make proposal @@ -423,6 +436,7 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte if len(hash) == 0 { // +2/3 prevoted nil. Just unlock. cs.LockedBlock = nil + cs.LockedBlockPartSet = nil return nil } else if cs.ProposalBlock.HashesTo(hash) { // +2/3 prevoted for proposal block @@ -433,15 +447,16 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte return nil } cs.LockedBlock = cs.ProposalBlock + cs.LockedBlockPartSet = cs.ProposalBlockPartSet return hash } else if cs.LockedBlock.HashesTo(hash) { // +2/3 prevoted for already locked block - // cs.LockedBlock = cs.LockedBlock return hash } else { // We don't have the block that hashes to hash. // Unlock if we're locked. cs.LockedBlock = nil + cs.LockedBlockPartSet = nil return nil } } else { @@ -454,15 +469,15 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte // and returns the committed block. // Commit is not finalized until FinalizeCommit() is called. // 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() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round != round { + if cs.Height != height { return nil } 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 // (unless voters are duplicitous). @@ -473,10 +488,13 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte { // TODO: Identify these strange cases. var block *Block + var blockPartSet *PartSet if cs.LockedBlock.HashesTo(hash) { block = cs.LockedBlock + blockPartSet = cs.LockedBlockPartSet } else if cs.ProposalBlock.HashesTo(hash) { block = cs.ProposalBlock + blockPartSet = cs.ProposalBlockPartSet } else { return nil } @@ -487,11 +505,17 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte { return nil } + // Keep block in cs.Proposal* + if !cs.ProposalBlock.HashesTo(hash) { + cs.ProposalBlock = block + cs.ProposalBlockPartSet = blockPartSet + } + // Save to blockStore cs.blockStore.SaveBlock(block) // Save the state - cs.stagedState.Save(commitTime) + cs.stagedState.Save() // Update mempool. cs.mempool.ResetForBlockAndState(block, cs.stagedState) @@ -502,12 +526,26 @@ func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) []byte { return nil } -// After TryCommit(), if successful, must call this in order to -// update the RoundState. -func (cs *ConsensusState) RunActionFinalize(height uint32, round uint16) { +func (cs *ConsensusState) RunActionCommitWait(height uint32) { cs.mtx.Lock() 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 } diff --git a/consensus/state_test.go b/consensus/state_test.go index 7f505a70..06cea1fc 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -18,7 +18,7 @@ func randAccountDetail(id uint64, status byte) (*state.AccountDetail, *state.Pri return &state.AccountDetail{ Account: account, Sequence: RandUInt(), - Balance: RandUInt64() + 1000, // At least 1000. + Balance: 1000, Status: status, }, privAccount } @@ -38,7 +38,7 @@ func randGenesisState(numAccounts int, numValidators int) (*state.State, []*stat } } s0 := state.GenesisState(db, time.Now(), accountDetails) - s0.Save(time.Now()) + s0.Save() 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() priv := NewPrivValidator(privAccounts[0], db_.NewMemDB()) cs.SetPrivValidator(priv) @@ -175,7 +175,7 @@ func TestRunActionPrecommit(t *testing.T) { for i := 0; i < 7; i++ { vote := &Vote{ Height: 1, - Round: uint16(i), + Round: 0, Type: VoteTypePrevote, BlockHash: cs.ProposalBlock.Hash(), } @@ -191,15 +191,11 @@ func TestRunActionPrecommit(t *testing.T) { checkRoundState(t, cs, 1, 0, RoundStepPrecommit) // Test RunActionCommit failures: - blockHash = cs.RunActionCommit(1, 1) - if blockHash != nil { - t.Errorf("RunActionCommit should fail for wrong round") - } - blockHash = cs.RunActionCommit(2, 0) + blockHash = cs.RunActionCommit(2) if blockHash != nil { t.Errorf("RunActionCommit should fail for wrong height") } - blockHash = cs.RunActionCommit(1, 0) + blockHash = cs.RunActionCommit(1) if blockHash != nil { t.Errorf("RunActionCommit should fail, not enough commits") } @@ -208,7 +204,7 @@ func TestRunActionPrecommit(t *testing.T) { for i := 0; i < 7; i++ { vote := &Vote{ Height: 1, - Round: uint16(i), + Round: 0, Type: VoteTypePrecommit, BlockHash: cs.ProposalBlock.Hash(), } @@ -217,10 +213,37 @@ func TestRunActionPrecommit(t *testing.T) { } // Test RunActionCommit success: - blockHash = cs.RunActionCommit(1, 0) + blockHash = cs.RunActionCommit(1) if len(blockHash) == 0 { t.Errorf("RunActionCommit should have succeeded") } 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) } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index f330c21b..9a8d449f 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -17,6 +17,7 @@ import ( // 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. // TODO: test majority calculations etc. +// NOTE: assumes that the sum total of voting power does not exceed MaxUInt64. type VoteSet struct { height uint32 round uint16 diff --git a/state/state.go b/state/state.go index b706869e..658a1b96 100644 --- a/state/state.go +++ b/state/state.go @@ -43,9 +43,9 @@ func (txErr InvalidTxError) Error() string { // NOTE: not goroutine-safe. type State struct { DB db_.DB - Height uint32 // Last known block height - BlockHash []byte // Last known block hash - CommitTime time.Time + Height uint32 // Last known block height + BlockHash []byte // Last known block hash + BlockTime time.Time // LastKnown block time BondedValidators *ValidatorSet UnbondingValidators *ValidatorSet 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, Height: 0, BlockHash: nil, - CommitTime: genesisTime, + BlockTime: genesisTime, BondedValidators: NewValidatorSet(validators), UnbondingValidators: NewValidatorSet(nil), accountDetails: accountDetails, @@ -94,8 +94,8 @@ func LoadState(db db_.DB) *State { var n int64 var err error s.Height = ReadUInt32(reader, &n, &err) - s.CommitTime = ReadTime(reader, &n, &err) s.BlockHash = ReadByteSlice(reader, &n, &err) + s.BlockTime = ReadTime(reader, &n, &err) s.BondedValidators = ReadValidatorSet(reader, &n, &err) s.UnbondingValidators = ReadValidatorSet(reader, &n, &err) accountDetailsHash := ReadByteSlice(reader, &n, &err) @@ -110,17 +110,14 @@ func LoadState(db db_.DB) *State { } // Save this state into the db. -// For convenience, the commitTime (required by ConsensusAgent) -// is saved here. -func (s *State) Save(commitTime time.Time) { - s.CommitTime = commitTime +func (s *State) Save() { s.accountDetails.Save() var buf bytes.Buffer var n int64 var err error WriteUInt32(&buf, s.Height, &n, &err) - WriteTime(&buf, commitTime, &n, &err) WriteByteSlice(&buf, s.BlockHash, &n, &err) + WriteTime(&buf, s.BlockTime, &n, &err) WriteBinary(&buf, s.BondedValidators, &n, &err) WriteBinary(&buf, s.UnbondingValidators, &n, &err) WriteByteSlice(&buf, s.accountDetails.Hash(), &n, &err) @@ -134,8 +131,8 @@ func (s *State) Copy() *State { return &State{ DB: s.DB, Height: s.Height, - CommitTime: s.CommitTime, BlockHash: s.BlockHash, + BlockTime: s.BlockTime, BondedValidators: s.BondedValidators.Copy(), UnbondingValidators: s.UnbondingValidators.Copy(), accountDetails: s.accountDetails.Copy(), @@ -397,6 +394,7 @@ func (s *State) AppendBlock(b *Block, checkStateHash bool) error { s.Height = b.Height s.BlockHash = b.Hash() + s.BlockTime = b.Time return nil } @@ -418,7 +416,7 @@ func (s *State) SetAccountDetail(accDet *AccountDetail) (updated bool) { } // Returns a hash that represents the state data, -// excluding Height, BlockHash, and CommitTime. +// excluding Height, BlockHash. func (s *State) Hash() []byte { hashables := []merkle.Hashable{ s.BondedValidators, diff --git a/state/state_test.go b/state/state_test.go index 580a2ed5..c9685d14 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -38,7 +38,7 @@ func randGenesisState(numAccounts int, numValidators int) (*State, []*PrivAccoun } } s0 := GenesisState(db, time.Now(), accountDetails) - s0.Save(time.Now()) + s0.Save() return s0, privAccounts } @@ -110,8 +110,7 @@ func TestGenesisSaveLoad(t *testing.T) { } // Save s0 - commitTime := time.Now() - s0.Save(commitTime) + s0.Save() // Sanity check s0 //s0.DB.(*db_.MemDB).Print() @@ -125,10 +124,6 @@ func TestGenesisSaveLoad(t *testing.T) { // Load s1 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 if s0.Height != s1.Height { t.Error("Height mismatch") diff --git a/state/validator_set.go b/state/validator_set.go index ae9e0fd1..560993d0 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -10,6 +10,7 @@ import ( ) // Not goroutine-safe. +// TODO: consider validator Accum overflow? type ValidatorSet struct { validators merkle.Tree proposer *Validator // Whoever has the highest Accum.