From 5790ea9f43580e6258c397d5475b8c46686cd025 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 4 Jun 2015 13:36:47 -0700 Subject: [PATCH 01/24] consensus refactor: reconstruct LastCommits upon restart --- consensus/reactor.go | 31 ++++++--------------- consensus/state.go | 51 +++++++++++++++++++++++++--------- consensus/vote_set.go | 56 +++++++++++++++++++++++++------------- consensus/vote_set_test.go | 22 +++++++-------- 4 files changed, 95 insertions(+), 65 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index 8671123c..8cae3cbb 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -524,28 +524,15 @@ OUTER_LOOP: // Catchup logic if prs.Height != 0 && !prs.HasAllCatchupCommits { - // If peer is lagging by height 1, match our LastCommits or SeenValidation to peer's Commits. - if rs.Height == prs.Height+1 && rs.LastCommits.Size() > 0 { - // If there are lastcommits to send... - if trySendVote(prs.Height, rs.LastCommits, prs.Commits) { - continue OUTER_LOOP - } else { - ps.SetHasAllCatchupCommits(prs.Height) - } - } - - // Or, if peer is lagging by 1 and we don't have LastCommits, send SeenValidation. - if rs.Height == prs.Height+1 && rs.LastCommits.Size() == 0 { - // Load the blockMeta for block at prs.Height - blockMeta := conR.blockStore.LoadBlockMeta(prs.Height) - // Load the seen validation for prs.Height - validation := conR.blockStore.LoadSeenValidation(prs.Height) - log.Debug("Loaded SeenValidation for catch-up", "height", prs.Height, "blockMeta", blockMeta, "validation", validation) - - if trySendCommitFromValidation(blockMeta, validation, prs.Commits) { - continue OUTER_LOOP - } else { - ps.SetHasAllCatchupCommits(prs.Height) + // If peer is lagging by height 1 + if rs.Height == prs.Height+1 { + if rs.LastCommits.Size() > 0 { + // Sync peer to rs.LastCommits + if trySendVote(prs.Height, rs.LastCommits, prs.Commits) { + continue OUTER_LOOP + } else { + ps.SetHasAllCatchupCommits(prs.Height) + } } } diff --git a/consensus/state.go b/consensus/state.go index de7b5fd2..78527f07 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -268,9 +268,38 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto newStepCh: make(chan *RoundState, 1), } cs.updateToState(state, true) + cs.reconstructLastCommits(state) return cs } +// Reconstruct LastCommits from SeenValidation, which we saved along with the block, +// (which happens even before saving the state) +func (cs *ConsensusState) reconstructLastCommits(state *sm.State) { + if state.LastBlockHeight == 0 { + return + } + lastCommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypeCommit, state.LastBondedValidators) + seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) + for idx, commit := range seenValidation.Commits { + commitVote := &types.Vote{ + Height: state.LastBlockHeight, + Round: commit.Round, + Type: types.VoteTypeCommit, + BlockHash: state.LastBlockHash, + BlockParts: state.LastBlockParts, + Signature: commit.Signature, + } + added, _, err := lastCommits.AddByIndex(uint(idx), commitVote) + if !added || err != nil { + panic(Fmt("Failed to reconstruct LastCommits: %v", err)) + } + } + if !lastCommits.HasTwoThirdsMajority() { + panic("Failed to reconstruct LastCommits: Does not have +2/3 maj") + } + cs.LastCommits = lastCommits +} + func (cs *ConsensusState) GetState() *sm.State { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -645,13 +674,9 @@ func (cs *ConsensusState) RunActionPropose(height uint, round uint) { // Make the validation from LastCommits validation = cs.LastCommits.MakeValidation() } else { - // Upon reboot, we may have to use SeenValidation - validation = cs.blockStore.LoadSeenValidation(height - 1) - if validation == nil { - // We just don't have any validation for the previous block - log.Debug("Cannot propose anything: No validation for the previous block.") - return - } + // We just don't have any validation for the previous block + log.Debug("Cannot propose anything: No validation for the previous block.") + return } txs := cs.mempoolReactor.Mempool.GetProposalTxs() block = &types.Block{ @@ -1024,14 +1049,14 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, switch vote.Type { case types.VoteTypePrevote: // Prevotes checks for height+round match. - added, index, err = cs.Prevotes.Add(address, vote) + added, index, err = cs.Prevotes.AddByAddress(address, vote) if added { log.Debug(Fmt("Added prevote: %v", cs.Prevotes.StringShort())) } return case types.VoteTypePrecommit: // Precommits checks for height+round match. - added, index, err = cs.Precommits.Add(address, vote) + added, index, err = cs.Precommits.AddByAddress(address, vote) if added { log.Debug(Fmt("Added precommit: %v", cs.Precommits.StringShort())) } @@ -1040,9 +1065,9 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, if vote.Height == cs.Height { // No need to check if vote.Round < cs.Round ... // Prevotes && Precommits already checks that. - cs.Prevotes.Add(address, vote) - cs.Precommits.Add(address, vote) - added, index, err = cs.Commits.Add(address, vote) + cs.Prevotes.AddByAddress(address, vote) + cs.Precommits.AddByAddress(address, vote) + added, index, err = cs.Commits.AddByAddress(address, vote) if added && cs.Commits.HasTwoThirdsMajority() && cs.CommitTime.IsZero() { cs.CommitTime = time.Now() log.Debug(Fmt("Set CommitTime to %v", cs.CommitTime)) @@ -1061,7 +1086,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, return } if vote.Height+1 == cs.Height { - added, index, err = cs.LastCommits.Add(address, vote) + added, index, err = cs.LastCommits.AddByAddress(address, vote) log.Debug(Fmt("Added lastCommits: %v", cs.LastCommits.StringShort())) return } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index ca2f44eb..39f7747b 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -70,14 +70,46 @@ func (voteSet *VoteSet) Size() uint { } } -// True if added, false if not. -// Returns ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] +// Returns added=true, index if vote was added +// Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] +// CONTRACT: if err == nil, added == true // NOTE: vote should not be mutated after adding. -// Returns the validator index of the vote unless error is set. -func (voteSet *VoteSet) Add(address []byte, vote *types.Vote) (bool, uint, error) { +func (voteSet *VoteSet) AddByIndex(valIndex uint, vote *types.Vote) (added bool, index uint, err error) { voteSet.mtx.Lock() defer voteSet.mtx.Unlock() + return voteSet.addByIndex(valIndex, vote) +} + +// Returns added=true, index if vote was added +// Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] +// CONTRACT: if err == nil, added == true +// NOTE: vote should not be mutated after adding. +func (voteSet *VoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + + // Ensure that signer is a validator. + valIndex, val := voteSet.valSet.GetByAddress(address) + if val == nil { + return false, 0, types.ErrVoteInvalidAccount + } + + return voteSet.addVote(val, valIndex, vote) +} + +func (voteSet *VoteSet) addByIndex(valIndex uint, vote *types.Vote) (bool, uint, error) { + // Ensure that signer is a validator. + _, val := voteSet.valSet.GetByIndex(valIndex) + if val == nil { + return false, 0, types.ErrVoteInvalidAccount + } + + return voteSet.addVote(val, valIndex, vote) +} + +func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vote) (bool, uint, error) { + // Make sure the step matches. (or that vote is commit && round < voteSet.round) if vote.Height != voteSet.height || (vote.Type != types.VoteTypeCommit && vote.Round != voteSet.round) || @@ -86,22 +118,12 @@ func (voteSet *VoteSet) Add(address []byte, vote *types.Vote) (bool, uint, error return false, 0, types.ErrVoteUnexpectedStep } - // Ensure that signer is a validator. - valIndex, val := voteSet.valSet.GetByAddress(address) - if val == nil { - return false, 0, types.ErrVoteInvalidAccount - } - // Check signature. if !val.PubKey.VerifyBytes(account.SignBytes(config.GetString("chain_id"), vote), vote.Signature) { // Bad signature. return false, 0, types.ErrVoteInvalidSignature } - return voteSet.addVote(valIndex, vote) -} - -func (voteSet *VoteSet) addVote(valIndex uint, vote *types.Vote) (bool, uint, error) { // If vote already exists, return false. if existingVote := voteSet.votes[valIndex]; existingVote != nil { if bytes.Equal(existingVote.BlockHash, vote.BlockHash) { @@ -115,10 +137,6 @@ func (voteSet *VoteSet) addVote(valIndex uint, vote *types.Vote) (bool, uint, er } // Add vote. - _, val := voteSet.valSet.GetByIndex(valIndex) - if val == nil { - panic(fmt.Sprintf("Missing validator for index %v", valIndex)) - } voteSet.votes[valIndex] = vote voteSet.votesBitArray.SetIndex(valIndex, true) blockKey := string(vote.BlockHash) + string(binary.BinaryBytes(vote.BlockParts)) @@ -144,7 +162,7 @@ func (voteSet *VoteSet) AddFromCommits(commits *VoteSet) { continue } if commit.Round < voteSet.round { - voteSet.addVote(uint(valIndex), commit) + voteSet.addByIndex(uint(valIndex), commit) } } } diff --git a/consensus/vote_set_test.go b/consensus/vote_set_test.go index 212fcf66..8cc919f3 100644 --- a/consensus/vote_set_test.go +++ b/consensus/vote_set_test.go @@ -51,7 +51,7 @@ func withBlockParts(vote *types.Vote, blockParts types.PartSetHeader) *types.Vot func signAddVote(privVal *sm.PrivValidator, vote *types.Vote, voteSet *VoteSet) (bool, error) { privVal.SignVoteUnsafe(config.GetString("chain_id"), vote) - added, _, err := voteSet.Add(privVal.Address, vote) + added, _, err := voteSet.AddByAddress(privVal.Address, vote) return added, err } @@ -197,31 +197,31 @@ func TestBadVotes(t *testing.T) { vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: nil} added, err := signAddVote(privValidators[0], vote, voteSet) if !added || err != nil { - t.Errorf("Expected Add() to succeed") + t.Errorf("Expected VoteSet.Add to succeed") } // val0 votes again for some block. added, err = signAddVote(privValidators[0], withBlockHash(vote, RandBytes(32)), voteSet) if added || err == nil { - t.Errorf("Expected Add() to fail, dupeout.") + t.Errorf("Expected VoteSet.Add to fail, dupeout.") } // val1 votes on another height added, err = signAddVote(privValidators[1], withHeight(vote, height+1), voteSet) if added { - t.Errorf("Expected Add() to fail, wrong height") + t.Errorf("Expected VoteSet.Add to fail, wrong height") } // val2 votes on another round added, err = signAddVote(privValidators[2], withRound(vote, round+1), voteSet) if added { - t.Errorf("Expected Add() to fail, wrong round") + t.Errorf("Expected VoteSet.Add to fail, wrong round") } // val3 votes of another type. added, err = signAddVote(privValidators[3], withType(vote, types.VoteTypePrecommit), voteSet) if added { - t.Errorf("Expected Add() to fail, wrong type") + t.Errorf("Expected VoteSet.Add to fail, wrong type") } } @@ -243,35 +243,35 @@ func TestAddCommitsToPrevoteVotes(t *testing.T) { vote = &types.Vote{Height: height - 1, Round: round, Type: types.VoteTypeCommit, BlockHash: nil} added, _ := signAddVote(privValidators[6], vote, voteSet) if added { - t.Errorf("Expected Add() to fail, wrong height.") + t.Errorf("Expected VoteSet.Add to fail, wrong height.") } // Attempt to add a commit from val6 at a later round vote = &types.Vote{Height: height, Round: round + 1, Type: types.VoteTypeCommit, BlockHash: nil} added, _ = signAddVote(privValidators[6], vote, voteSet) if added { - t.Errorf("Expected Add() to fail, cannot add future round vote.") + t.Errorf("Expected VoteSet.Add to fail, cannot add future round vote.") } // Attempt to add a commit from val6 for currrent height/round. vote = &types.Vote{Height: height, Round: round, Type: types.VoteTypeCommit, BlockHash: nil} added, err := signAddVote(privValidators[6], vote, voteSet) if added || err == nil { - t.Errorf("Expected Add() to fail, only prior round commits can be added.") + t.Errorf("Expected VoteSet.Add to fail, only prior round commits can be added.") } // Add commit from val6 at a previous round vote = &types.Vote{Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: nil} added, err = signAddVote(privValidators[6], vote, voteSet) if !added || err != nil { - t.Errorf("Expected Add() to succeed, commit for prior rounds are relevant.") + t.Errorf("Expected VoteSet.Add to succeed, commit for prior rounds are relevant.") } // Also add commit from val7 for previous round. vote = &types.Vote{Height: height, Round: round - 2, Type: types.VoteTypeCommit, BlockHash: nil} added, err = signAddVote(privValidators[7], vote, voteSet) if !added || err != nil { - t.Errorf("Expected Add() to succeed. err: %v", err) + t.Errorf("Expected VoteSet.Add to succeed. err: %v", err) } // We should have 2/3 majority From 1b95c09160e22609c164591e77a849ed06a74047 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 4 Jun 2015 13:36:57 -0700 Subject: [PATCH 02/24] consensus refactor: add HeightVoteSet --- consensus/height_vote_set.go | 146 +++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 consensus/height_vote_set.go diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go new file mode 100644 index 00000000..5b5bbdb6 --- /dev/null +++ b/consensus/height_vote_set.go @@ -0,0 +1,146 @@ +package consensus + +import ( + "strings" + "sync" + + . "github.com/tendermint/tendermint/common" + sm "github.com/tendermint/tendermint/state" + "github.com/tendermint/tendermint/types" +) + +type RoundVoteSet struct { + Prevotes *VoteSet + Precommits *VoteSet +} + +// Keeps track of VoteSets for all the rounds of a height. +// We add the commit votes to all the affected rounds, +// and for new rounds carry over the commit set. Commits have +// an associated round, so the performance hit won't be O(rounds). +type HeightVoteSet struct { + height uint + valSet *sm.ValidatorSet + + mtx sync.Mutex + round uint // max tracked round + roundVoteSets map[uint]RoundVoteSet // keys: [0...round] + commits *VoteSet // all commits for height +} + +func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { + hvs := &HeightVoteSet{ + height: height, + valSet: valSet, + roundVoteSets: make(map[uint]RoundVoteSet), + commits: NewVoteSet(height, 0, types.VoteTypeCommit, valSet), + } + hvs.SetRound(0) + return hvs +} + +func (hvs *HeightVoteSet) Height() uint { + return hvs.height +} + +func (hvs *HeightVoteSet) Round() uint { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + return hvs.round +} + +// Create more RoundVoteSets up to round with all commits carried over. +func (hvs *HeightVoteSet) SetRound(round uint) { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + if hvs.round != 0 && (round < hvs.round+1) { + panic("SetRound() must increment hvs.round") + } + for r := hvs.round + 1; r <= round; r++ { + prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) + prevotes.AddFromCommits(hvs.commits) + precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) + precommits.AddFromCommits(hvs.commits) + hvs.roundVoteSets[r] = RoundVoteSet{ + Prevotes: prevotes, + Precommits: precommits, + } + } + hvs.round = round +} + +// CONTRACT: if err == nil, added == true +func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + voteSet := hvs.getVoteSet(vote.Round, vote.Type) + if voteSet == nil { + return + } + added, index, err = voteSet.AddByAddress(address, vote) + if err != nil { + return + } + // If vote is commit, also add to all prevote/precommit for future rounds. + if vote.Type == types.VoteTypeCommit { + for round := vote.Round + 1; round <= hvs.round; round++ { + voteSet := hvs.getVoteSet(round, types.VoteTypePrevote) + _, _, err = voteSet.AddByAddress(address, vote) + if err != nil { + // TODO slash for prevote after commit + log.Warn("Prevote after commit", "address", address, "vote", vote) + } + voteSet = hvs.getVoteSet(round, types.VoteTypePrecommit) + _, _, err = voteSet.AddByAddress(address, vote) + if err != nil { + // TODO slash for prevote after commit + log.Warn("Prevote after commit", "address", address, "vote", vote) + } + } + } + return +} + +func (hvs *HeightVoteSet) GetVoteSet(round uint, type_ byte) *VoteSet { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + return hvs.getVoteSet(round, type_) +} + +func (hvs *HeightVoteSet) getVoteSet(round uint, type_ byte) *VoteSet { + if type_ == types.VoteTypeCommit { + return hvs.commits + } + rvs, ok := hvs.roundVoteSets[round] + if !ok { + return nil + } + switch type_ { + case types.VoteTypePrevote: + return rvs.Prevotes + case types.VoteTypePrecommit: + return rvs.Precommits + default: + panic(Fmt("Unexpected vote type %X", type_)) + } +} + +func (hvs *HeightVoteSet) String() string { + return hvs.StringIndented("") +} + +func (hvs *HeightVoteSet) StringIndented(indent string) string { + vsStrings := make([]string, 0, hvs.round*2+1) + vsStrings = append(vsStrings, hvs.commits.StringShort()) + for round := uint(0); round <= hvs.round; round++ { + voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() + vsStrings = append(vsStrings, voteSetString) + voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() + vsStrings = append(vsStrings, voteSetString) + } + return Fmt(`HeightVoteSet{H:%v R:0~%v +%s %v +%s}`, + indent, strings.Join(vsStrings, "\n"+indent+" "), + indent) +} From 01b5540ffe202f05ad537e0f6fff49f09a4dacc3 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 5 Jun 2015 14:15:40 -0700 Subject: [PATCH 03/24] consensus/state is 2-step asynchronous --- blockchain/store.go | 16 +- consensus/height_vote_set.go | 58 +- consensus/pol.go | 101 --- consensus/pol_test.go | 213 ------ consensus/state.go | 1285 +++++++++++++++------------------- consensus/state_test.go | 171 +---- consensus/types/proposal.go | 11 +- consensus/vote_set.go | 76 +- consensus/vote_set_test.go | 67 +- state/execution.go | 47 +- state/priv_validator.go | 10 - state/state.go | 2 +- state/state_test.go | 26 +- state/validator_set.go | 18 +- types/block.go | 67 +- types/vote.go | 5 - 16 files changed, 706 insertions(+), 1467 deletions(-) delete mode 100644 consensus/pol.go delete mode 100644 consensus/pol_test.go diff --git a/blockchain/store.go b/blockchain/store.go index f9d54cd2..f0f4e77f 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -18,9 +18,9 @@ Simple low level store for blocks. There are three types of information stored: - BlockMeta: Meta information about each block - Block part: Parts of each block, aggregated w/ PartSet - - Validation: The Validation part of each block, for gossiping commit votes + - Validation: The Validation part of each block, for gossiping precommit votes -Currently the commit signatures are duplicated in the Block parts as +Currently the precommit signatures are duplicated in the Block parts as well as the Validation. In the future this may change, perhaps by moving the Validation data outside the Block. */ @@ -101,7 +101,7 @@ func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta { return meta } -// NOTE: the Commit-vote heights are for the block at `height-1` +// NOTE: the Precommit-vote heights are for the block at `height-1` // Since these are included in the subsequent block, the height // is off by 1. func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation { @@ -118,7 +118,7 @@ func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation { return validation } -// NOTE: the Commit-vote heights are for the block at `height` +// NOTE: the Precommit-vote heights are for the block at `height` func (bs *BlockStore) LoadSeenValidation(height uint) *types.Validation { var n int64 var err error @@ -134,12 +134,10 @@ func (bs *BlockStore) LoadSeenValidation(height uint) *types.Validation { } // blockParts: Must be parts of the block -// seenValidation: The +2/3 commits that were seen which finalized the height. +// seenValidation: The +2/3 precommits that were seen which committed at height. // If all the nodes restart after committing a block, -// we need this to reload the commits to catch-up nodes to the +// we need this to reload the precommits to catch-up nodes to the // most recent height. Otherwise they'd stall at H-1. -// Also good to have to debug consensus issues & punish wrong-signers -// whose commits weren't included in the block. func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, seenValidation *types.Validation) { height := block.Height if height != bs.height+1 { @@ -163,7 +161,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s blockValidationBytes := binary.BinaryBytes(block.Validation) bs.db.Set(calcBlockValidationKey(height), blockValidationBytes) - // Save seen validation (seen +2/3 commits) + // Save seen validation (seen +2/3 precommits for block) seenValidationBytes := binary.BinaryBytes(seenValidation) bs.db.Set(calcSeenValidationKey(height), seenValidationBytes) diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index 5b5bbdb6..dbcc112f 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -15,9 +15,6 @@ type RoundVoteSet struct { } // Keeps track of VoteSets for all the rounds of a height. -// We add the commit votes to all the affected rounds, -// and for new rounds carry over the commit set. Commits have -// an associated round, so the performance hit won't be O(rounds). type HeightVoteSet struct { height uint valSet *sm.ValidatorSet @@ -25,7 +22,6 @@ type HeightVoteSet struct { mtx sync.Mutex round uint // max tracked round roundVoteSets map[uint]RoundVoteSet // keys: [0...round] - commits *VoteSet // all commits for height } func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { @@ -33,7 +29,6 @@ func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { height: height, valSet: valSet, roundVoteSets: make(map[uint]RoundVoteSet), - commits: NewVoteSet(height, 0, types.VoteTypeCommit, valSet), } hvs.SetRound(0) return hvs @@ -49,7 +44,7 @@ func (hvs *HeightVoteSet) Round() uint { return hvs.round } -// Create more RoundVoteSets up to round with all commits carried over. +// Create more RoundVoteSets up to round. func (hvs *HeightVoteSet) SetRound(round uint) { hvs.mtx.Lock() defer hvs.mtx.Unlock() @@ -58,9 +53,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) { } for r := hvs.round + 1; r <= round; r++ { prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) - prevotes.AddFromCommits(hvs.commits) precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) - precommits.AddFromCommits(hvs.commits) hvs.roundVoteSets[r] = RoundVoteSet{ Prevotes: prevotes, Precommits: precommits, @@ -78,39 +71,35 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote) (added return } added, index, err = voteSet.AddByAddress(address, vote) - if err != nil { - return - } - // If vote is commit, also add to all prevote/precommit for future rounds. - if vote.Type == types.VoteTypeCommit { - for round := vote.Round + 1; round <= hvs.round; round++ { - voteSet := hvs.getVoteSet(round, types.VoteTypePrevote) - _, _, err = voteSet.AddByAddress(address, vote) - if err != nil { - // TODO slash for prevote after commit - log.Warn("Prevote after commit", "address", address, "vote", vote) - } - voteSet = hvs.getVoteSet(round, types.VoteTypePrecommit) - _, _, err = voteSet.AddByAddress(address, vote) - if err != nil { - // TODO slash for prevote after commit - log.Warn("Prevote after commit", "address", address, "vote", vote) - } - } - } return } -func (hvs *HeightVoteSet) GetVoteSet(round uint, type_ byte) *VoteSet { +func (hvs *HeightVoteSet) Prevotes(round uint) *VoteSet { hvs.mtx.Lock() defer hvs.mtx.Unlock() - return hvs.getVoteSet(round, type_) + return hvs.getVoteSet(round, types.VoteTypePrevote) +} + +func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + return hvs.getVoteSet(round, types.VoteTypePrecommit) +} + +// Last round that has +2/3 prevotes for a particular block or nik. +// Returns -1 if no such round exists. +func (hvs *HeightVoteSet) POLRound() int { + hvs.mtx.Lock() + defer hvs.mtx.Unlock() + for r := hvs.round; r >= 0; r-- { + if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() { + return int(r) + } + } + return -1 } func (hvs *HeightVoteSet) getVoteSet(round uint, type_ byte) *VoteSet { - if type_ == types.VoteTypeCommit { - return hvs.commits - } rvs, ok := hvs.roundVoteSets[round] if !ok { return nil @@ -130,8 +119,7 @@ func (hvs *HeightVoteSet) String() string { } func (hvs *HeightVoteSet) StringIndented(indent string) string { - vsStrings := make([]string, 0, hvs.round*2+1) - vsStrings = append(vsStrings, hvs.commits.StringShort()) + vsStrings := make([]string, 0, hvs.round*2) for round := uint(0); round <= hvs.round; round++ { voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() vsStrings = append(vsStrings, voteSetString) diff --git a/consensus/pol.go b/consensus/pol.go deleted file mode 100644 index 7314eb48..00000000 --- a/consensus/pol.go +++ /dev/null @@ -1,101 +0,0 @@ -package consensus - -import ( - "fmt" - - "github.com/tendermint/tendermint/account" - "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" - sm "github.com/tendermint/tendermint/state" - "github.com/tendermint/tendermint/types" -) - -// Each signature of a POL (proof-of-lock, see whitepaper) is -// either a prevote or a commit. -// Commits require an additional round which is strictly less than -// the POL round. Prevote rounds are equal to the POL round. -type POLVoteSignature struct { - Round uint `json:"round"` - Signature account.SignatureEd25519 `json:"signature"` -} - -// Proof of lock. -// +2/3 of validators' prevotes for a given blockhash (or nil) -type POL struct { - Height uint `json:"height"` - Round uint `json:"round"` - BlockHash []byte `json:"block_hash"` // Could be nil, which makes this a proof of unlock. - BlockParts types.PartSetHeader `json:"block_parts"` // When BlockHash is nil, this is zero. - Votes []POLVoteSignature `json:"votes"` // Prevote and commit signatures in ValidatorSet order. -} - -// Returns whether +2/3 have prevoted/committed for BlockHash. -func (pol *POL) Verify(valSet *sm.ValidatorSet) error { - - if uint(len(pol.Votes)) != valSet.Size() { - return fmt.Errorf("Invalid POL votes count: Expected %v, got %v", - valSet.Size(), len(pol.Votes)) - } - - talliedVotingPower := uint64(0) - prevoteDoc := account.SignBytes(config.GetString("chain_id"), &types.Vote{ - Height: pol.Height, Round: pol.Round, Type: types.VoteTypePrevote, - BlockHash: pol.BlockHash, - BlockParts: pol.BlockParts, - }) - seenValidators := map[string]struct{}{} - - for idx, vote := range pol.Votes { - // vote may be zero, in which case skip. - if vote.Signature.IsZero() { - continue - } - voteDoc := prevoteDoc - _, val := valSet.GetByIndex(uint(idx)) - - // Commit vote? - if vote.Round < pol.Round { - voteDoc = account.SignBytes(config.GetString("chain_id"), &types.Vote{ - Height: pol.Height, Round: vote.Round, Type: types.VoteTypeCommit, - BlockHash: pol.BlockHash, - BlockParts: pol.BlockParts, - }) - } else if vote.Round > pol.Round { - return fmt.Errorf("Invalid commit round %v for POL %v", vote.Round, pol) - } - - // Validate - if _, seen := seenValidators[string(val.Address)]; seen { - return fmt.Errorf("Duplicate validator for vote %v for POL %v", vote, pol) - } - - if !val.PubKey.VerifyBytes(voteDoc, vote.Signature) { - return fmt.Errorf("Invalid signature for vote %v for POL %v", vote, pol) - } - - // Tally - seenValidators[string(val.Address)] = struct{}{} - talliedVotingPower += val.VotingPower - } - - if talliedVotingPower > valSet.TotalVotingPower()*2/3 { - return nil - } else { - return fmt.Errorf("Invalid POL, insufficient voting power %v, needed %v", - talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) - } - -} - -func (pol *POL) StringShort() string { - if pol == nil { - return "nil-POL" - } else { - return fmt.Sprintf("POL{H:%v R:%v BH:%X}", pol.Height, pol.Round, - Fingerprint(pol.BlockHash), pol.BlockParts) - } -} - -func (pol *POL) MakePartSet() *types.PartSet { - return types.NewPartSetFromData(binary.BinaryBytes(pol)) -} diff --git a/consensus/pol_test.go b/consensus/pol_test.go deleted file mode 100644 index 5b56969d..00000000 --- a/consensus/pol_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package consensus - -import ( - "github.com/tendermint/tendermint/binary" - . "github.com/tendermint/tendermint/common" - _ "github.com/tendermint/tendermint/config/tendermint_test" - sm "github.com/tendermint/tendermint/state" - "github.com/tendermint/tendermint/types" - - "bytes" - "testing" -) - -// NOTE: see consensus/test.go for common test methods. - -// Convenience method. -// Signs the vote and sets the POL's vote at the desired index -// Returns the POLVoteSignature pointer, so you can modify it afterwards. -func signAddPOLVoteSignature(val *sm.PrivValidator, valSet *sm.ValidatorSet, vote *types.Vote, pol *POL) *POLVoteSignature { - vote = vote.Copy() - err := val.SignVote(config.GetString("chain_id"), vote) - if err != nil { - panic(err) - } - idx, _ := valSet.GetByAddress(val.Address) // now we have the index - pol.Votes[idx] = POLVoteSignature{vote.Round, vote.Signature} - return &pol.Votes[idx] -} - -func TestVerifyVotes(t *testing.T) { - height, round := uint(1), uint(0) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with -2/3 votes. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash, - } - for i := 0; i < 6; i++ { - signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - } - - // Check that validation fails. - if err := pol.Verify(valSet); err == nil { - t.Errorf("Expected POL.Verify() to fail, not enough votes.") - } - - // Insert another vote to make +2/3 - signAddPOLVoteSignature(privValidators[7], valSet, voteProto, pol) - - // Check that validation succeeds. - if err := pol.Verify(valSet); err != nil { - t.Errorf("POL.Verify() failed: %v", err) - } -} - -func TestVerifyInvalidVote(t *testing.T) { - height, round := uint(1), uint(0) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 votes with the wrong signature. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - polVoteSig.Signature[0] += byte(0x01) // mutated! - } - - // Check that validation fails. - if err := pol.Verify(valSet); err == nil { - t.Errorf("Expected POL.Verify() to fail, wrong signatures.") - } -} - -func TestVerifyCommits(t *testing.T) { - height, round := uint(1), uint(2) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 votes. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - } - - // Check that validation succeeds. - if err := pol.Verify(valSet); err != nil { - t.Errorf("POL.Verify() failed: %v", err) - } -} - -func TestVerifyInvalidCommits(t *testing.T) { - height, round := uint(1), uint(2) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 votes with the wrong signature. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - polVoteSig.Signature[0] += byte(0x01) - } - - // Check that validation fails. - if err := pol.Verify(valSet); err == nil { - t.Errorf("Expected POL.Verify() to fail, wrong signatures.") - } -} - -func TestVerifyInvalidCommitRounds(t *testing.T) { - height, round := uint(1), uint(2) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 commits for the current round. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round, Type: types.VoteTypeCommit, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - } - - // Check that validation fails. - if err := pol.Verify(valSet); err == nil { - t.Errorf("Expected POL.Verify() to fail, same round.") - } -} - -func TestVerifyInvalidCommitRounds2(t *testing.T) { - height, round := uint(1), uint(2) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 commits for future round. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round + 1, Type: types.VoteTypeCommit, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - polVoteSig := signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - polVoteSig.Round += 1 // mutate round - } - - // Check that validation fails. - if err := pol.Verify(valSet); err == nil { - t.Errorf("Expected POL.Verify() to fail, future round.") - } -} - -func TestReadWrite(t *testing.T) { - height, round := uint(1), uint(2) - _, valSet, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // Make a POL with +2/3 votes. - blockHash := RandBytes(32) - pol := &POL{ - Height: height, Round: round, BlockHash: blockHash, - Votes: make([]POLVoteSignature, valSet.Size()), - } - voteProto := &types.Vote{ - Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash, - } - for i := 0; i < 7; i++ { - signAddPOLVoteSignature(privValidators[i], valSet, voteProto, pol) - } - - // Write it to a buffer. - buf, n, err := new(bytes.Buffer), new(int64), new(error) - binary.WriteBinary(pol, buf, n, err) - if *err != nil { - t.Fatalf("Failed to write POL: %v", *err) - } - - // Read from buffer. - pol2 := binary.ReadBinary(&POL{}, buf, n, err).(*POL) - if *err != nil { - t.Fatalf("Failed to read POL: %v", *err) - } - - // Check that validation succeeds. - if err := pol2.Verify(valSet); err != nil { - t.Errorf("POL.Verify() failed: %v", err) - } -} diff --git a/consensus/state.go b/consensus/state.go index 78527f07..9dc4dd34 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -2,55 +2,95 @@ Consensus State Machine Overview: -* Propose, Prevote, Precommit represent state machine stages. (aka RoundStep, or step). - Each take a predetermined amount of time depending on the round number. -* The Commit step can be entered by two means: - 1. After the Precommit step, +2/3 Precommits were found - 2. At any time, +2/3 Commits were found -* Once in the Commit stage, two conditions must both be satisfied - before proceeding to the next height NewHeight. -* The Propose step of the next height does not begin until - at least Delta duration *after* +2/3 Commits were found. - The step stays at NewHeight until this timeout occurs before - proceeding to Propose. -* The NewHeight is a transition period after the height is incremented, - where the node still receives late commits before potentially proposing. - The height should be incremented because a block had been - "committed by the network", and clients should see that - reflected as a new height. +* NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). +* To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. +* During NewHeight/NewRound/Propose/Prevote/Precommit: + * Nodes gossip the locked proposal, if locked on a proposal. + * Nodes gossip the proposal proposed by the designated proposer for that round. + * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 for catch-up) +* Upon each state transition, the height/round/step is broadcast to neighboring peers. +* The set of +2/3 of precommits from the same round for a block "commits the block". + +* NewRound: + * Set up new round. --> Then, goto Propose + +* Propose: + * Upon entering Propose: + * The designated proposer proposes a block. + * The Propose step ends: + * After `timeoutPropose` after entering Propose. --> Then, goto Prevote + * After receiving proposal block and POL prevotes are ready. --> Then, goto Prevote + * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round + * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round + * After +2/3 precommits received for a particular block. --> Then, goto Commit + +* Prevote: + * Upon entering Prevote, each validator broadcasts its prevote vote. + * If the validator is locked on a block, it prevotes that. + * Else, if the proposed block from the previous step is good, it prevotes that. + * Else, if the proposal is invalid or wasn't received on time, it prevotes . + * The Prevote step ends: + * After +2/3 prevotes for a particular block or . --> Then, goto Precommit + * After `timeoutPrevote` after receiving any +2/3 prevotes. --> Then, goto Precommit + * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round + * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round + * After +2/3 precommits received for a particular block. --> Then, goto Commit + +* Precommit: + * Upon entering Precommit, each validator broadcasts its precommit vote. + * If the validator had seen +2/3 of prevotes for a particular block, + it locks (changes lock to) that block and precommits that block. + * Else, if the validator had seen +2/3 of prevotes for nil, it unlocks and precommits . + * Else, if +2/3 of prevotes for a particular block or nil is not received on time, + it precommits what it's locked on, or . + * The Precommit step ends: + * After +2/3 precommits for a particular block. --> Then, goto Commit + * After +2/3 precommits for . --> Then, goto NewRound next round + * After `timeoutPrecommit` after receiving any +2/3 precommits. --> Then, goto NewRound next round + * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round + * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round + +* Commit: + * Set CommitTime = now + * Wait until block is received, then goto NewHeight + +* NewHeight: + * Upon entering NewHeight, + * Move Precommits to LastPrecommits and increment height. + * Wait until `CommitTime+timeoutCommit` to receive straggler commits. --> Then, goto NewRound round 0 + +* Proof of Safety: + If a good validator commits at round R, it's because it saw +2/3 of precommits for round R. + This implies that (assuming tolerance bounds) +1/3 of honest nodes are still locked at round R+1. + These locked validators will remain locked until they see +2/3 prevote for something + else, but this won't happen because +1/3 are locked and honest. + +* Proof of Liveness: + Lemma 1: If +1/3 good nodes are locked on two different blocks, the proposers' POLRound will + eventually cause nodes locked from the earlier round to unlock. + -> `timeoutProposalR` increments with round R, while the block.size && POL prevote size + are fixed, so eventually we'll be able to "fully gossip" the block & POL. + TODO: cap the block.size at something reasonable. + Lemma 2: If a good node is at round R, neighboring good nodes will soon catch up to round R. +-------------------------------------+ - | | - v |(Wait til CommitTime + Delta) + v |(Wait til `CommmitTime+timeoutCommit`) +-----------+ +-----+-----+ +----------> | Propose +--------------+ | NewHeight | | +-----------+ | +-----------+ | | ^ - | | | - | | | - |(Else) v | + |(Else, after timeoutPrecommit) v | +-----+-----+ +-----------+ | | Precommit | <------------------------+ Prevote | | +-----+-----+ +-----------+ | - |(If +2/3 Precommits found) | - | | - | + (When +2/3 Commits found) | - | | | - v v | - +------------------------------------------------------------------------------+ - | Commit | | - | | | - | +----------------+ * Save Block | | - | |Get Block Parts |---> * Stage Block +--+ + | - | +----------------+ * Broadcast Commit | * Setup New Height | - | | * Move Commits set to | - | +--> LastCommits to continue | - | | collecting commits | - | +-----------------+ | * Broadcast New State | - | |Get +2/3 Commits |--> * Set CommitTime +--+ | - | +-----------------+ | - | | - +------------------------------------------------------------------------------+ + |(When +2/3 Precommits for block found) | + v | + +--------------------------------------------------------------------+ + | Commit | + | | + | * Set CommitTime = now; | + | * Wait for block, then stage/save/commit block; | + +--------------------------------------------------------------------+ */ @@ -60,7 +100,6 @@ import ( "bytes" "errors" "fmt" - "math" "sync" "sync/atomic" "time" @@ -76,15 +115,13 @@ import ( "github.com/tendermint/tendermint/types" ) -const ( - roundDeadlinePrevote = float64(1.0 / 3.0) // When the prevote is due. - roundDeadlinePrecommit = float64(2.0 / 3.0) // When the precommit vote is due. -) - var ( - RoundDuration0 = 10 * time.Second // The first round is 60 seconds long. - RoundDurationDelta = 3 * time.Second // Each successive round lasts 15 seconds longer. - newHeightDelta = RoundDuration0 / 3 // The time to wait between commitTime and startTime of next consensus rounds. + timeoutPropose = 3000 * time.Millisecond // Maximum duration of RoundStepPropose + timeoutPrevote0 = 1000 * time.Millisecond // After any +2/3 prevotes received, wait this long for stragglers. + timeoutPrevoteDelta = 0500 * time.Millisecond // timeoutPrevoteN is timeoutPrevote0 + timeoutPrevoteDelta*N + timeoutPrecommit0 = 1000 * time.Millisecond // After any +2/3 precommits received, wait this long for stragglers. + timeoutPrecommitDelta = 0500 * time.Millisecond // timeoutPrecommitN is timeoutPrecommit0 + timeoutPrecommitDelta*N + timeoutCommit = 2000 * time.Millisecond // After +2/3 commits received for committed block, wait this long for stragglers in the next height's RoundStepNewHeight. ) var ( @@ -97,12 +134,15 @@ var ( type RoundStepType uint8 // These must be numeric, ordered. const ( - RoundStepNewHeight = RoundStepType(0x01) // Round0 for new height started, wait til CommitTime + Delta - RoundStepNewRound = RoundStepType(0x02) // Pseudostep, immediately goes to RoundStepPropose - RoundStepPropose = RoundStepType(0x03) // Did propose, gossip proposal - RoundStepPrevote = RoundStepType(0x04) // Did prevote, gossip prevotes - RoundStepPrecommit = RoundStepType(0x05) // Did precommit, gossip precommits - RoundStepCommit = RoundStepType(0x06) // Entered commit state machine + RoundStepNewHeight = RoundStepType(0x01) // Wait til CommitTime + timeoutCommit + RoundStepNewRound = RoundStepType(0x02) // Setup new round and go to RoundStepPropose + RoundStepPropose = RoundStepType(0x03) // Did propose, gossip proposal + RoundStepPrevote = RoundStepType(0x04) // Did prevote, gossip prevotes + RoundStepPrevoteWait = RoundStepType(0x05) // Did receive any +2/3 prevotes, start timeout + RoundStepPrecommit = RoundStepType(0x06) // Did precommit, gossip precommits + RoundStepPrecommitWait = RoundStepType(0x07) // Did receive any +2/3 precommits, start timeout + RoundStepCommit = RoundStepType(0x08) // Entered commit state machine + // NOTE: RoundStepNewHeight acts as RoundStepCommitWait. ) func (rs RoundStepType) String() string { @@ -115,8 +155,12 @@ func (rs RoundStepType) String() string { return "RoundStepPropose" case RoundStepPrevote: return "RoundStepPrevote" + case RoundStepPrevoteWait: + return "RoundStepPrevoteWait" case RoundStepPrecommit: return "RoundStepPrecommit" + case RoundStepPrecommitWait: + return "RoundStepPrecommitWait" case RoundStepCommit: return "RoundStepCommit" default: @@ -124,51 +168,6 @@ func (rs RoundStepType) String() string { } } -//----------------------------------------------------------------------------- -// RoundAction enum type - -type RoundActionType string - -const ( - RoundActionPropose = RoundActionType("PR") // Propose and goto RoundStepPropose - RoundActionPrevote = RoundActionType("PV") // Prevote and goto RoundStepPrevote - RoundActionPrecommit = RoundActionType("PC") // Precommit and goto RoundStepPrecommit - RoundActionTryCommit = RoundActionType("TC") // Goto RoundStepCommit, or RoundStepPropose for next round. - RoundActionCommit = RoundActionType("CM") // Goto RoundStepCommit upon +2/3 commits - RoundActionTryFinalize = RoundActionType("TF") // Maybe goto RoundStepPropose for next round. -) - -func (rat RoundActionType) String() string { - switch rat { - case RoundActionPropose: - return "RoundActionPropose" - case RoundActionPrevote: - return "RoundActionPrevote" - case RoundActionPrecommit: - return "RoundActionPrecommit" - case RoundActionTryCommit: - return "RoundActionTryCommit" - case RoundActionCommit: - return "RoundActionCommit" - case RoundActionTryFinalize: - return "RoundActionTryFinalize" - default: - panic(Fmt("Unknown RoundAction %X", rat)) - } -} - -//----------------------------------------------------------------------------- - -type RoundAction struct { - Height uint // The block height for which consensus is reaching for. - Round uint // The round number at given height. - Action RoundActionType // Action to perform. -} - -func (ra RoundAction) String() string { - return Fmt("RoundAction{H:%v R:%v A:%v}", ra.Height, ra.Round, ra.Action) -} - //----------------------------------------------------------------------------- // Immutable when returned from ConsensusState.GetRoundState() @@ -177,20 +176,15 @@ type RoundState struct { Round uint Step RoundStepType StartTime time.Time - CommitTime time.Time // Time when +2/3 commits were found + CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found Validators *sm.ValidatorSet Proposal *Proposal ProposalBlock *types.Block ProposalBlockParts *types.PartSet - ProposalPOL *POL - ProposalPOLParts *types.PartSet LockedBlock *types.Block LockedBlockParts *types.PartSet - LockedPOL *POL // Rarely needed, so no LockedPOLParts. - Prevotes *VoteSet - Precommits *VoteSet - Commits *VoteSet - LastCommits *VoteSet + Votes *HeightVoteSet + LastPrecommits *VoteSet // Last precommits for Height-1 } func (rs *RoundState) String() string { @@ -205,13 +199,9 @@ func (rs *RoundState) StringIndented(indent string) string { %s Validators: %v %s Proposal: %v %s ProposalBlock: %v %v -%s ProposalPOL: %v %v %s LockedBlock: %v %v -%s LockedPOL: %v -%s Prevotes: %v -%s Precommits: %v -%s Commits: %v -%s LastCommits: %v +%s Votes: %v +%s LastPrecommits: %v %s}`, indent, rs.Height, rs.Round, rs.Step, indent, rs.StartTime, @@ -219,13 +209,9 @@ func (rs *RoundState) StringIndented(indent string) string { indent, rs.Validators.StringIndented(indent+" "), indent, rs.Proposal, indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), - indent, rs.ProposalPOLParts.StringShort(), rs.ProposalPOL.StringShort(), indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), - indent, rs.LockedPOL.StringShort(), - indent, rs.Prevotes.StringIndented(indent+" "), - indent, rs.Precommits.StringIndented(indent+" "), - indent, rs.Commits.StringIndented(indent+" "), - indent, rs.LastCommits.StringShort(), + indent, rs.Votes.StringIndented(indent+" "), + indent, rs.LastPrecommits.StringShort(), indent) } @@ -245,15 +231,13 @@ type ConsensusState struct { blockStore *bc.BlockStore mempoolReactor *mempl.MempoolReactor privValidator *sm.PrivValidator - runActionCh chan RoundAction newStepCh chan *RoundState mtx sync.Mutex RoundState - state *sm.State // State until height-1. - stagedBlock *types.Block // Cache last staged block. - stagedState *sm.State // Cache result of staged block. - lastCommitVoteHeight uint // Last called commitVoteBlock() or saveCommitVoteBlock() on. + state *sm.State // State until height-1. + stagedBlock *types.Block // Cache last staged block. + stagedState *sm.State // Cache result of staged block. evsw events.Fireable evc *events.EventCache // set in stageBlock and passed into state @@ -264,40 +248,40 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto quit: make(chan struct{}), blockStore: blockStore, mempoolReactor: mempoolReactor, - runActionCh: make(chan RoundAction, 1), - newStepCh: make(chan *RoundState, 1), + newStepCh: make(chan *RoundState, 10), } cs.updateToState(state, true) - cs.reconstructLastCommits(state) + cs.maybeRebond() + cs.reconstructLastPrecommits(state) return cs } -// Reconstruct LastCommits from SeenValidation, which we saved along with the block, +// Reconstruct LastPrecommits from SeenValidation, which we saved along with the block, // (which happens even before saving the state) -func (cs *ConsensusState) reconstructLastCommits(state *sm.State) { +func (cs *ConsensusState) reconstructLastPrecommits(state *sm.State) { if state.LastBlockHeight == 0 { return } - lastCommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypeCommit, state.LastBondedValidators) + lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) - for idx, commit := range seenValidation.Commits { - commitVote := &types.Vote{ + for idx, precommit := range seenValidation.Precommits { + precommitVote := &types.Vote{ Height: state.LastBlockHeight, - Round: commit.Round, - Type: types.VoteTypeCommit, + Round: seenValidation.Round, + Type: types.VoteTypePrecommit, BlockHash: state.LastBlockHash, BlockParts: state.LastBlockParts, - Signature: commit.Signature, + Signature: precommit.Signature, } - added, _, err := lastCommits.AddByIndex(uint(idx), commitVote) + added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) if !added || err != nil { - panic(Fmt("Failed to reconstruct LastCommits: %v", err)) + panic(Fmt("Failed to reconstruct LastPrecommits: %v", err)) } } - if !lastCommits.HasTwoThirdsMajority() { - panic("Failed to reconstruct LastCommits: Does not have +2/3 maj") + if !lastPrecommits.HasTwoThirdsMajority() { + panic("Failed to reconstruct LastPrecommits: Does not have +2/3 maj") } - cs.LastCommits = lastCommits + cs.LastPrecommits = lastPrecommits } func (cs *ConsensusState) GetState() *sm.State { @@ -324,10 +308,20 @@ func (cs *ConsensusState) NewStepCh() chan *RoundState { func (cs *ConsensusState) Start() { if atomic.CompareAndSwapUint32(&cs.started, 0, 1) { log.Info("Starting ConsensusState") - go cs.stepTransitionRoutine() + cs.scheduleRound0() } } +func (cs *ConsensusState) scheduleRound0(height uint) { + sleepDuration := cs.StartTime.Sub(time.Now()) + go func() { + if sleepDuration > 0 { + time.Sleep(sleepDuration) + } + cs.EnterNewRound(height, 0) + }() +} + func (cs *ConsensusState) Stop() { if atomic.CompareAndSwapUint32(&cs.stopped, 0, 1) { log.Info("Stopping ConsensusState") @@ -339,186 +333,23 @@ func (cs *ConsensusState) IsStopped() bool { return atomic.LoadUint32(&cs.stopped) == 1 } -func (cs *ConsensusState) queueAction(ra RoundAction) { - go func() { - cs.runActionCh <- ra - }() -} - -// Source of all round state transitions (and votes). -func (cs *ConsensusState) stepTransitionRoutine() { - - // For clarity, all state transitions that happen after some timeout are here. - // Schedule the next action by pushing a RoundAction{} to cs.runActionCh. - scheduleNextAction := func() { - // NOTE: rs must be fetched in the same callstack as the caller. - rs := cs.getRoundState() - go func() { - // NOTE: We can push directly to runActionCh because - // we're running in a separate goroutine, which avoids deadlocks. - round, roundStartTime, RoundDuration, _, elapsedRatio := calcRoundInfo(rs.StartTime) - log.Debug("Scheduling next action", "height", rs.Height, "round", round, "step", rs.Step, "roundStartTime", roundStartTime, "elapsedRatio", elapsedRatio) - switch rs.Step { - case RoundStepNewHeight: - // We should run RoundActionPropose when rs.StartTime passes. - if elapsedRatio < 0 { - // startTime is in the future. - time.Sleep(time.Duration((-1.0 * elapsedRatio) * float64(RoundDuration))) - } - cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPropose} - case RoundStepNewRound: - // Pseudostep: Immediately goto propose. - cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPropose} - case RoundStepPropose: - // Wake up when it's time to vote. - time.Sleep(time.Duration((roundDeadlinePrevote - elapsedRatio) * float64(RoundDuration))) - cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrevote} - case RoundStepPrevote: - // Wake up when it's time to precommit. - time.Sleep(time.Duration((roundDeadlinePrecommit - elapsedRatio) * float64(RoundDuration))) - cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrecommit} - case RoundStepPrecommit: - // Wake up when the round is over. - time.Sleep(time.Duration((1.0 - elapsedRatio) * float64(RoundDuration))) - cs.runActionCh <- RoundAction{rs.Height, rs.Round, RoundActionTryCommit} - case RoundStepCommit: - // There's nothing to scheudle, we're waiting for - // ProposalBlockParts.IsComplete() && - // Commits.HasTwoThirdsMajority() - panic("The next action from RoundStepCommit is not scheduled by time") - default: - panic("Should not happen") - } - }() - } - - if cs.getRoundState().Step < RoundStepCommit { - scheduleNextAction() - } else { - // Race condition with receipt of commits, maybe. - // We shouldn't have to schedule anything. - } - - // NOTE: All ConsensusState.RunAction*() calls 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 concurrent - // due to gossip routines. -ACTION_LOOP: - for { - var roundAction RoundAction - select { - case roundAction = <-cs.runActionCh: - case <-cs.quit: - return - } - - height, round, action := roundAction.Height, roundAction.Round, roundAction.Action - rs := cs.GetRoundState() - - // Continue if action is not relevant - if height != rs.Height { - log.Debug("Discarding round action: Height mismatch", "height", rs.Height, "roundAction", roundAction) - continue - } - // If action <= RoundActionPrecommit, the round must match too. - if action <= RoundActionPrecommit && round != rs.Round { - log.Debug("Discarding round action: Round mismatch", "round", rs.Round, "roundAction", roundAction) - continue - } - - log.Info("Running round action", "height", rs.Height, "round", rs.Round, "step", rs.Step, "roundAction", roundAction, "startTime", rs.StartTime) - - // Run action - switch action { - case RoundActionPropose: - if rs.Step != RoundStepNewHeight && rs.Step != RoundStepNewRound { - continue ACTION_LOOP - } - cs.RunActionPropose(rs.Height, rs.Round) - scheduleNextAction() - continue ACTION_LOOP - - case RoundActionPrevote: - if rs.Step >= RoundStepPrevote { - continue ACTION_LOOP - } - cs.RunActionPrevote(rs.Height, rs.Round) - scheduleNextAction() - continue ACTION_LOOP - - case RoundActionPrecommit: - if rs.Step >= RoundStepPrecommit { - continue ACTION_LOOP - } - cs.RunActionPrecommit(rs.Height, rs.Round) - scheduleNextAction() - continue ACTION_LOOP - - case RoundActionTryCommit: - if rs.Step >= RoundStepCommit { - continue ACTION_LOOP - } - if rs.Precommits.HasTwoThirdsMajority() { - // Enter RoundStepCommit and commit. - cs.RunActionCommit(rs.Height) - continue ACTION_LOOP - } else { - // Could not commit, move onto next round. - cs.SetupNewRound(rs.Height, rs.Round+1) - // cs.Step is now at RoundStepNewRound - scheduleNextAction() - continue ACTION_LOOP - } - - case RoundActionCommit: - if rs.Step >= RoundStepCommit { - continue ACTION_LOOP - } - // Enter RoundStepCommit and commit. - cs.RunActionCommit(rs.Height) - continue ACTION_LOOP - - case RoundActionTryFinalize: - if cs.TryFinalizeCommit(rs.Height) { - // Now at new height - // cs.Step is at RoundStepNewHeight or RoundStepNewRound. - // fire some events! - go func() { - newBlock := cs.blockStore.LoadBlock(cs.state.LastBlockHeight) - cs.evsw.FireEvent(types.EventStringNewBlock(), newBlock) - cs.evc.Flush() - }() - scheduleNextAction() - continue ACTION_LOOP - } else { - // do not schedule next action. - continue ACTION_LOOP - } - - default: - panic("Unknown action") - } - - // For clarity, ensure that all switch cases call "continue" - panic("Should not happen.") - } -} - // Updates ConsensusState and increments height to match that of state. -// If calculated round is greater than 0 (based on BlockTime or calculated StartTime) -// then also sets up the appropriate round, and cs.Step becomes RoundStepNewRound. -// Otherwise the round is 0 and cs.Step becomes RoundStepNewHeight. +// The round becomes 0 and cs.Step becomes RoundStepNewHeight. func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { - // Sanity check state. + // SANITY CHECK if contiguous && cs.Height > 0 && cs.Height != state.LastBlockHeight { panic(Fmt("updateToState() expected state height of %v but found %v", cs.Height, state.LastBlockHeight)) } + // END SANITY CHECK // Reset fields based on state. validators := state.BondedValidators height := state.LastBlockHeight + 1 // next desired block height + lastPrecommits := (*VoteSet)(nil) + if contiguous && cs.Votes != nil { + lastPrecommits = cs.Votes.Precommits(cs.Round) + } // RoundState fields cs.Height = height @@ -526,84 +357,51 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { cs.Step = RoundStepNewHeight if cs.CommitTime.IsZero() { // "Now" makes it easier to sync up dev nodes. - // We add newHeightDelta to allow transactions + // We add timeoutCommit to allow transactions // to be gathered for the first block. // And alternative solution that relies on clocks: - // cs.StartTime = state.LastBlockTime.Add(newHeightDelta) - cs.StartTime = time.Now().Add(newHeightDelta) + // cs.StartTime = state.LastBlockTime.Add(timeoutCommit) + cs.StartTime = time.Now().Add(timeoutCommit) } else { - cs.StartTime = cs.CommitTime.Add(newHeightDelta) + cs.StartTime = cs.CommitTime.Add(timeoutCommit) } cs.CommitTime = time.Time{} cs.Validators = validators cs.Proposal = nil cs.ProposalBlock = nil cs.ProposalBlockParts = nil - cs.ProposalPOL = nil - cs.ProposalPOLParts = nil cs.LockedBlock = nil cs.LockedBlockParts = nil - cs.LockedPOL = nil - cs.Prevotes = NewVoteSet(height, 0, types.VoteTypePrevote, validators) - cs.Precommits = NewVoteSet(height, 0, types.VoteTypePrecommit, validators) - cs.LastCommits = cs.Commits - cs.Commits = NewVoteSet(height, 0, types.VoteTypeCommit, validators) + cs.Votes = NewHeightVoteSet(height, validators) + cs.LastPrecommits = lastPrecommits cs.state = state cs.stagedBlock = nil cs.stagedState = nil - - // Update the round if we need to. - round := calcRound(cs.StartTime) - if round > 0 { - cs.setupNewRound(round) - } - - // If we've timed out, then send rebond tx. - if cs.privValidator != nil && cs.state.UnbondingValidators.HasAddress(cs.privValidator.Address) { - rebondTx := &types.RebondTx{ - Address: cs.privValidator.Address, - Height: cs.Height, - } - err := cs.privValidator.SignRebondTx(cs.state.ChainID, rebondTx) - if err == nil { - err := cs.mempoolReactor.BroadcastTx(rebondTx) - if err != nil { - log.Error("Failed to broadcast RebondTx", - "height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err) - } else { - log.Info("Signed and broadcast RebondTx", - "height", cs.Height, "round", cs.Round, "tx", rebondTx) - } - } else { - log.Warn("Error signing RebondTx", "height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err) - } - } } -// After the call cs.Step becomes RoundStepNewRound. -func (cs *ConsensusState) setupNewRound(round uint) { - // Sanity check - if round == 0 { - panic("setupNewRound() should never be called for round 0") +// If we're unbonded, broadcast RebondTx. +func (cs *ConsensusState) maybeRebond() { + if cs.privValidator == nil || !cs.state.UnbondingValidators.HasAddress(cs.privValidator.Address) { + return + } + rebondTx := &types.RebondTx{ + Address: cs.privValidator.Address, + Height: cs.Height, + } + err := cs.privValidator.SignRebondTx(cs.state.ChainID, rebondTx) + if err == nil { + err := cs.mempoolReactor.BroadcastTx(rebondTx) + if err != nil { + log.Error("Failed to broadcast RebondTx", + "height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err) + } else { + log.Info("Signed and broadcast RebondTx", + "height", cs.Height, "round", cs.Round, "tx", rebondTx) + } + } else { + log.Warn("Error signing RebondTx", "height", cs.Height, "round", cs.Round, "tx", rebondTx, "error", err) } - - // Increment all the way to round. - validators := cs.Validators.Copy() - validators.IncrementAccum(round - cs.Round) - - cs.Round = round - cs.Step = RoundStepNewRound - cs.Validators = validators - cs.Proposal = nil - cs.ProposalBlock = nil - cs.ProposalBlockParts = nil - cs.ProposalPOL = nil - cs.ProposalPOLParts = nil - cs.Prevotes = NewVoteSet(cs.Height, round, types.VoteTypePrevote, validators) - cs.Prevotes.AddFromCommits(cs.Commits) - cs.Precommits = NewVoteSet(cs.Height, round, types.VoteTypePrecommit, validators) - cs.Precommits.AddFromCommits(cs.Commits) } func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { @@ -614,105 +412,96 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { //----------------------------------------------------------------------------- -// Set up the round to desired round and set step to RoundStepNewRound -func (cs *ConsensusState) SetupNewRound(height uint, desiredRound uint) bool { +// Enter: +2/3 precommits for nil from previous round +// Enter: `timeoutPrecommits` after any +2/3 precommits +// Enter: `commitTime+timeoutCommit` from NewHeight +// NOTE: cs.StartTime was already set for height. +func (cs *ConsensusState) EnterNewRound(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height { - return false - } - if desiredRound <= cs.Round { - return false - } - cs.setupNewRound(desiredRound) - // c.Step is now RoundStepNewRound - cs.newStepCh <- cs.getRoundState() - return true -} - -func (cs *ConsensusState) RunActionPropose(height uint, round uint) { - cs.mtx.Lock() - defer cs.mtx.Unlock() - if cs.Height != height || cs.Round != round { + if cs.Height != height || cs.Round >= round { + log.Debug(Fmt("EnterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } + if now := time.Now(); cs.StartTime.After(now) { + log.Warn("Need to set a buffer and log.Warn() here for sanity.", "startTime", cs.StartTime, "now", now) + } + + // Increment validators if necessary + if cs.Round < round { + validators := cs.Validators.Copy() + validators.IncrementAccum(round - cs.Round) + } + + // Setup new round + cs.Round = round + cs.Step = RoundStepNewRound + cs.Validators = validators + cs.Proposal = nil + cs.ProposalBlock = nil + cs.ProposalBlockParts = nil + cs.Votes.SetRound(round + 1) // track next round. + + // Immediately go to EnterPropose. + go cs.EnterPropose(height, round) +} + +// Enter: from NewRound. +func (cs *ConsensusState) EnterPropose(height uint, round uint) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPropose) { + log.Debug(Fmt("EnterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + return + } + defer func() { + // Done EnterPropose: + cs.Round = round cs.Step = RoundStepPropose cs.newStepCh <- cs.getRoundState() + + // If we already have the proposal + POL, then goto Prevote + if cs.isProposalComplete() { + go cs.EnterPrevote(height, round) + } + }() + + // This step times out after `timeoutPropose` + go func() { + time.Sleep(timeoutPropose) + cs.EnterPrevote(height, round) }() // Nothing to do if it's not our turn. if cs.privValidator == nil { return } + + // See if it is our turn to propose if !bytes.Equal(cs.Validators.Proposer().Address, cs.privValidator.Address) { - log.Debug("Not our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) + log.Debug("EnterPropose: Not our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) return } else { - log.Debug("Our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) + log.Debug("EnterPropose: Our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) } + // We are going to propose a block. + var block *types.Block var blockParts *types.PartSet - var pol *POL - var polParts *types.PartSet - // Decide on block and POL + // Decide on block if cs.LockedBlock != nil { // If we're locked onto a block, just choose that. - block = cs.LockedBlock - blockParts = cs.LockedBlockParts - pol = cs.LockedPOL + block, blockParts = cs.LockedBlock, cs.LockedBlockParts } else { - // Otherwise we should create a new proposal. - var validation *types.Validation - if cs.Height == 1 { - // We're creating a proposal for the first block. - // The validation is empty. - validation = &types.Validation{} - } else if cs.LastCommits.HasTwoThirdsMajority() { - // Make the validation from LastCommits - validation = cs.LastCommits.MakeValidation() - } else { - // We just don't have any validation for the previous block - log.Debug("Cannot propose anything: No validation for the previous block.") - return - } - txs := cs.mempoolReactor.Mempool.GetProposalTxs() - block = &types.Block{ - Header: &types.Header{ - ChainID: cs.state.ChainID, - Height: cs.Height, - Time: time.Now(), - Fees: 0, // TODO fees - NumTxs: uint(len(txs)), - LastBlockHash: cs.state.LastBlockHash, - LastBlockParts: cs.state.LastBlockParts, - StateHash: nil, // Will set afterwards. - }, - Validation: validation, - Data: &types.Data{ - Txs: txs, - }, - } - - // Set the types.Header.StateHash. - err := cs.state.SetBlockStateHash(block) - if err != nil { - log.Error("Error setting state hash", "error", err) - return - } - - blockParts = block.MakePartSet() - pol = cs.LockedPOL // If exists, is a PoUnlock. - } - - if pol != nil { - polParts = pol.MakePartSet() + // Create a new proposal block from state/txs from the mempool. + block, blockParts = cs.createProposalBlock() } // Make proposal - proposal := NewProposal(cs.Height, cs.Round, blockParts.Header(), polParts.Header()) + proposal := NewProposal(cs.Height, cs.Round, blockParts.Header(), cs.Votes.POLRound()) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) if err == nil { log.Info("Signed and set proposal", "height", cs.Height, "round", cs.Round, "proposal", proposal) @@ -721,36 +510,102 @@ func (cs *ConsensusState) RunActionPropose(height uint, round uint) { cs.Proposal = proposal cs.ProposalBlock = block cs.ProposalBlockParts = blockParts - cs.ProposalPOL = pol - cs.ProposalPOLParts = polParts } else { - log.Warn("Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err) + log.Warn("EnterPropose: Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err) } } +func (cs *ConsensusState) isProposalComplete() bool { + if cs.Proposal == nil || cs.ProposalBlock == nil { + return false + } + return cs.Votes.Prevote(cs.Proposal.POLRound).HasTwoThirdsMajority() +} + +// Create the next block to propose and return it. +// NOTE: make it side-effect free for clarity. +func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { + var validation *types.Validation + if cs.Height == 1 { + // We're creating a proposal for the first block. + // The validation is empty. + validation = &types.Validation{} + } else if cs.LastPrecommits.HasTwoThirdsMajority() { + // Make the validation from LastPrecommits + validation = cs.LastPrecommits.MakeValidation() + } else { + // This shouldn't happen. + log.Error("EnterPropose: Cannot propose anything: No validation for the previous block.") + return + } + txs := cs.mempoolReactor.Mempool.GetProposalTxs() + block = &types.Block{ + Header: &types.Header{ + ChainID: cs.state.ChainID, + Height: cs.Height, + Time: time.Now(), + Fees: 0, // TODO fees + NumTxs: uint(len(txs)), + LastBlockHash: cs.state.LastBlockHash, + LastBlockParts: cs.state.LastBlockParts, + StateHash: nil, // Will set afterwards. + }, + Validation: validation, + Data: &types.Data{ + Txs: txs, + }, + } + + // Set the block.Header.StateHash. + err := cs.state.ComputeBlockStateHash(block) + if err != nil { + log.Error("EnterPropose: Error setting state hash", "error", err) + return + } + + blockParts = block.MakePartSet() + return block, blockParts +} + +// Enter: `timeoutPropose` after start of Propose. +// Enter: proposal block and POL is ready. +// Enter: any +2/3 prevotes for next round. // Prevote for LockedBlock if we're locked, or ProposealBlock if valid. // Otherwise vote nil. -func (cs *ConsensusState) RunActionPrevote(height uint, round uint) { +func (cs *ConsensusState) EnterPrevote(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round != round { - panic(Fmt("RunActionPrevote(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)) + if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevote) { + log.Debug(Fmt("EnterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + return } + defer func() { + // Done EnterPrevote: + cs.Round = round cs.Step = RoundStepPrevote cs.newStepCh <- cs.getRoundState() + // Maybe immediately go to EnterPrevoteWait. + if cs.Votes.Prevotes(round).HasTwoThirdsAny() { + go cs.EnterPrevoteWait(height, round) + } }() + // Sign and broadcast vote as necessary + cs.doPrevote(height, round) +} + +func (cs *ConsensusState) doPrevote(height uint, round uint) { // If a block is locked, prevote that. if cs.LockedBlock != nil { - log.Debug("Block was locked") + log.Debug("EnterPrevote: Block was locked") cs.signAddVote(types.VoteTypePrevote, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) return } // If ProposalBlock is nil, prevote nil. if cs.ProposalBlock == nil { - log.Warn("ProposalBlock is nil") + log.Warn("EnterPrevote: ProposalBlock is nil") cs.signAddVote(types.VoteTypePrevote, nil, types.PartSetHeader{}) return } @@ -759,7 +614,7 @@ func (cs *ConsensusState) RunActionPrevote(height uint, round uint) { err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts) if err != nil { // ProposalBlock is invalid, prevote nil. - log.Warn("ProposalBlock is invalid", "error", err) + log.Warn("EnterPrevote: ProposalBlock is invalid", "error", err) cs.signAddVote(types.VoteTypePrevote, nil, types.PartSetHeader{}) return } @@ -769,107 +624,181 @@ func (cs *ConsensusState) RunActionPrevote(height uint, round uint) { return } -// Lock & Precommit the ProposalBlock if we have enough prevotes for it, -// or unlock an existing lock if +2/3 of prevotes were nil. -func (cs *ConsensusState) RunActionPrecommit(height uint, round uint) { +// Enter: any +2/3 prevotes for next round. +func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round != round { - panic(Fmt("RunActionPrecommit(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)) + if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevoteWait) { + log.Debug(Fmt("EnterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + return + } + if !cs.Votes.Prevotes(round).HasTwoThirdsAny() { + panic(Fmt("EnterPrevoteWait(%v/%v), but Prevotes does not have any +2/3 votes", height, round)) } - defer func() { - cs.Step = RoundStepPrecommit - cs.newStepCh <- cs.getRoundState() - }() - hash, partsHeader, ok := cs.Prevotes.TwoThirdsMajority() - if !ok { - // If we don't have two thirds of prevotes, - // don't do anything at all. - log.Info("Insufficient prevotes for precommit") + // Done EnterPrevoteWait: + cs.Round = round + cs.Step = RoundStepPrevoteWait + cs.newStepCh <- cs.getRoundState() + + // After `timeoutPrevote`, EnterPrecommit() + go func() { + time.Sleep(timeoutPrevote) + cs.EnterPrecommit(height, round) + }() +} + +// Enter: +2/3 precomits for block or nil. +// Enter: `timeoutPrevote` after any +2/3 prevotes. +// Enter: any +2/3 precommits for next round. +// Lock & precommit the ProposalBlock if we have enough prevotes for it, +// else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, +// else, precommit locked block or nil otherwise. +func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommit) { + log.Debug(Fmt("EnterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } - // Remember this POL. (hash may be nil) - cs.LockedPOL = cs.Prevotes.MakePOL() + defer func() { + // Done EnterPrecommit: + cs.Round = round + cs.Step = RoundStepPrecommit + cs.newStepCh <- cs.getRoundState() + // Maybe immediately go to EnterPrecommitWait. + if cs.Votes.Precommits(round).HasTwoThirdsAny() { + go cs.EnterPrecommitWait(height, round) + } + }() - // If +2/3 prevoted nil. Just unlock. - if len(hash) == 0 { - if cs.LockedBlock == nil { - log.Info("+2/3 prevoted for nil.") + hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() + + // If we don't have two thirds of prevotes, just precommit locked block or nil + if !ok { + if cs.LockedBlock != nil { + log.Info("EnterPrecommit: No +2/3 prevotes during EnterPrecommit. Precommitting lock.") + cs.signAddVote(types.VoteTypePrecommit, cs.LockedBlock.Hash(), cs.LockedBlockParts.Header()) } else { - log.Info("+2/3 prevoted for nil. Unlocking") - cs.LockedBlock = nil - cs.LockedBlockParts = nil + log.Info("EnterPrecommit: No +2/3 prevotes during EnterPrecommit. Precommitting nil.") + cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) } return } + // +2/3 prevoted nil. Unlock and precommit nil. + if len(hash) == 0 { + if cs.LockedBlock == nil { + log.Info("EnterPrecommit: +2/3 prevoted for nil.") + } else { + log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking") + cs.LockedBlock = nil + cs.LockedBlockParts = nil + } + cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) + return + } + + // At this point, +2/3 prevoted for a particular block. + // If +2/3 prevoted for already locked block, precommit it. if cs.LockedBlock.HashesTo(hash) { - log.Info("+2/3 prevoted locked block.") + log.Info("EnterPrecommit: +2/3 prevoted locked block.") cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader) return } - // If +2/3 prevoted for cs.ProposalBlock, lock it and precommit it. - if !cs.ProposalBlock.HashesTo(hash) { - log.Warn("Proposal does not hash to +2/3 prevotes") - // We don't have the block that validators prevoted. - // Unlock if we're locked. - cs.LockedBlock = nil - cs.LockedBlockParts = nil + // If +2/3 prevoted for proposal block, stage and precommit it + if cs.ProposalBlock.HashesTo(hash) { + log.Info("EnterPrecommit: +2/3 prevoted proposal block.") + // Validate the block. + if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil { + panic(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err)) + } + cs.LockedBlock = cs.ProposalBlock + cs.LockedBlockParts = cs.ProposalBlockParts + cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader) return } - // Validate the block. - if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil { - // Prevent zombies. - log.Warn("+2/3 prevoted for an invalid block", "error", err) - return + // Otherwise, we need to fetch the +2/3 prevoted block. + // We don't have the block yet so we can't lock/precommit it. + cs.LockedBlock = nil + cs.LockedBlockParts = nil + if !cs.ProposalBlockParts.HasHeader(partsHeader) { + cs.ProposalBlock = nil + cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader) } - - cs.LockedBlock = cs.ProposalBlock - cs.LockedBlockParts = cs.ProposalBlockParts - cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader) + cs.signAddVote(types.VoteTypePrecommit, nil, PartSetHeader{}) return } -// Enter commit step. See the diagram for details. -// There are two ways to enter this step: -// * After the Precommit step with +2/3 precommits, or, -// * Upon +2/3 commits regardless of current step -// Either way this action is run at most once per round. -func (cs *ConsensusState) RunActionCommit(height uint) { +// Enter: any +2/3 precommits for next round. +func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height { - panic(Fmt("RunActionCommit(%v), expected %v", height, cs.Height)) + if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommitWait) { + log.Debug(Fmt("EnterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) + return } + if !cs.Votes.Precommits(round).HasTwoThirdsAny() { + panic(Fmt("EnterPrecommitWait(%v/%v), but Precommits does not have any +2/3 votes", height, round)) + } + + // Done EnterPrecommitWait: + cs.Round = round + cs.Step = RoundStepPrecommitWait + cs.newStepCh <- cs.getRoundState() + + // After `timeoutPrecommit`, EnterNewRound() + go func() { + time.Sleep(timeoutPrecommit) + // If we have +2/3 of precommits for a particular block (or nil), + // we already entered commit (or the next round). + // So just try to transition to the next round, + // which is what we'd do otherwise. + cs.EnterNewRound(height, round+1) + }() +} + +// Enter: +2/3 precommits for block +func (cs *ConsensusState) EnterCommit(height uint) { + cs.mtx.Lock() + defer cs.mtx.Unlock() + if cs.Height != height || cs.Step >= RoundStepCommit { + log.Debug(Fmt("EnterCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step)) + return + } + defer func() { + // Done Entercommit: + // keep ca.Round the same, it points to the right Precommits set. cs.Step = RoundStepCommit cs.newStepCh <- cs.getRoundState() + + // Maybe finalize immediately. + cs.TryFinalizeCommit(height) }() - // Sanity check. - // There are two ways to enter: - // 1. +2/3 precommits at the end of RoundStepPrecommit - // 2. +2/3 commits at any time + // SANITY CHECK hash, partsHeader, ok := cs.Precommits.TwoThirdsMajority() if !ok { - hash, partsHeader, ok = cs.Commits.TwoThirdsMajority() - if !ok { - panic("RunActionCommit() expects +2/3 precommits or commits") - } + panic("RunActionCommit() expects +2/3 precommits") } + // END SANITY CHECK - // Clear the Locked* fields and use cs.Proposed* + // The Locked* fields no longer matter. + // Move them over to ProposalBlock if they match the commit hash, + // otherwise they can now be cleared. if cs.LockedBlock.HashesTo(hash) { cs.ProposalBlock = cs.LockedBlock cs.ProposalBlockParts = cs.LockedBlockParts cs.LockedBlock = nil cs.LockedBlockParts = nil - cs.LockedPOL = nil + } else { + cs.LockedBlock = nil + cs.LockedBlockParts = nil } // If we don't have the block being committed, set up to get it. @@ -879,7 +808,6 @@ func (cs *ConsensusState) RunActionCommit(height uint) { // Set up ProposalBlockParts and keep waiting. cs.ProposalBlock = nil cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader) - } else { // We just need to keep waiting. } @@ -887,59 +815,66 @@ func (cs *ConsensusState) RunActionCommit(height uint) { // We have the block, so sign a Commit-vote. cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts) } - - // If we have the block AND +2/3 commits, queue RoundActionTryFinalize. - // Round will immediately become finalized. - if cs.ProposalBlock.HashesTo(hash) && cs.Commits.HasTwoThirdsMajority() { - cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) - } - } -// Returns true if Finalize happened, which increments height && sets -// the step to RoundStepNewHeight (or RoundStepNewRound, but probably not). -func (cs *ConsensusState) TryFinalizeCommit(height uint) bool { +// If we have the block AND +2/3 commits for it, finalize. +func (cs *ConsensusState) TryFinalizeCommit(height uint) { + if cs.ProposalBlock.HashesTo(hash) && cs.Commits.HasTwoThirdsMajority() { + go cs.FinalizeCommit(height) + } +} + +// Increment height and goto RoundStepNewHeight +func (cs *ConsensusState) FinalizeCommit(height uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height { - panic(Fmt("TryFinalizeCommit(%v), expected %v", height, cs.Height)) + if cs.Height != height || cs.Step != RoundStepCommit { + log.Debug(Fmt("FinalizeCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step)) + return } - if cs.Step == RoundStepCommit && - cs.Commits.HasTwoThirdsMajority() && - cs.ProposalBlockParts.IsComplete() { + hash, header, ok := cs.Commits.TwoThirdsMajority() - // Sanity check - if cs.ProposalBlock == nil { - panic(Fmt("Expected ProposalBlock to exist")) - } - hash, header, _ := cs.Commits.TwoThirdsMajority() - if !cs.ProposalBlock.HashesTo(hash) { - // XXX See: https://github.com/tendermint/tendermint/issues/44 - panic(Fmt("Expected ProposalBlock to hash to commit hash. Expected %X, got %X", hash, cs.ProposalBlock.Hash())) - } - if !cs.ProposalBlockParts.HasHeader(header) { - panic(Fmt("Expected ProposalBlockParts header to be commit header")) - } - - err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts) - if err == nil { - log.Debug(Fmt("Finalizing commit of block: %v", cs.ProposalBlock)) - // We have the block, so save/stage/sign-commit-vote. - cs.saveCommitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) - // Increment height. - cs.updateToState(cs.stagedState, true) - // cs.Step is now RoundStepNewHeight or RoundStepNewRound - cs.newStepCh <- cs.getRoundState() - return true - } else { - // Prevent zombies. - // TODO: Does this ever happen? - panic(Fmt("+2/3 committed an invalid block: %v", err)) - } + // SANITY CHECK + if !ok { + panic(Fmt("Cannot FinalizeCommit, commit does not have two thirds majority")) } - return false + if !cs.ProposalBlockParts.HasHeader(header) { + panic(Fmt("Expected ProposalBlockParts header to be commit header")) + } + if !cs.ProposalBlock.HashesTo(hash) { + panic(Fmt("Cannot FinalizeCommit, ProposalBlock does not hash to commit hash")) + } + if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil { + panic(Fmt("+2/3 committed an invalid block: %v", err)) + } + // END SANITY CHECK + + log.Debug(Fmt("Finalizing commit of block: %v", cs.ProposalBlock)) + // We have the block, so stage/save/commit-vote. + cs.saveBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) + cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) + // Fire off event + go func() { + cs.evsw.FireEvent(types.EventStringNewBlock(), cs.ProposalBlock) + cs.evc.Flush() + }(cs.ProposalBlock) + + // Increment height. + cs.updateToState(cs.stagedState, true) + + // If we're unbonded, broadcast RebondTx. + cs.maybeRebond() + + // By here, + // * cs.Height has been increment to height+1 + // * cs.Step is now RoundStepNewHeight + // * cs.StartTime is set to when we should start round0. + cs.newStepCh <- cs.getRoundState() + // Start round 0 when cs.StartTime. + go cs.scheduleRound0(height) + return } //----------------------------------------------------------------------------- @@ -959,7 +894,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } // We don't care about the proposal if we're already in RoundStepCommit. - if cs.Step == RoundStepCommit { + if cs.Step >= RoundStepCommit { return nil } @@ -970,12 +905,10 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { cs.Proposal = proposal cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockParts) - cs.ProposalPOLParts = types.NewPartSetFromHeader(proposal.POLParts) return nil } // NOTE: block is not necessarily valid. -// NOTE: This function may increment the height. func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *types.Part) (added bool, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -999,38 +932,13 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty var err error cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block) log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash()) - // If we're already in the commit step, try to finalize round. - if cs.Step == RoundStepCommit { - cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) + if cs.Step == RoundStepPropose { + if cs.isProposalComplete() { + go cs.EnterPrevote(height, round) + } + } else if cs.Step == RoundStepCommit { + cs.TryFinalizeCommit(height) } - // XXX If POL is valid, consider unlocking. - return true, err - } - return true, nil -} - -// NOTE: POL is not necessarily valid. -func (cs *ConsensusState) AddProposalPOLPart(height uint, round uint, part *types.Part) (added bool, err error) { - cs.mtx.Lock() - defer cs.mtx.Unlock() - - if cs.Height != height || cs.Round != round { - return false, nil - } - - // We're not expecting a POL part. - if cs.ProposalPOLParts == nil { - return false, nil // TODO: bad peer? Return error? - } - - added, err = cs.ProposalPOLParts.AddPart(part) - if err != nil { - return added, err - } - if added && cs.ProposalPOLParts.IsComplete() { - var n int64 - var err error - cs.ProposalPOL = binary.ReadBinary(&POL{}, cs.ProposalPOLParts.GetReader(), &n, &err).(*POL) return true, err } return true, nil @@ -1046,54 +954,78 @@ func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote) (added bool, //----------------------------------------------------------------------------- func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { - switch vote.Type { - case types.VoteTypePrevote: - // Prevotes checks for height+round match. - added, index, err = cs.Prevotes.AddByAddress(address, vote) + // A precommit for the previous height? + if vote.Height+1 == cs.Height && vote.Type == types.VoteTypePrecommit { + added, index, err = cs.LastPrecommits.AddByAddress(address, vote) if added { - log.Debug(Fmt("Added prevote: %v", cs.Prevotes.StringShort())) + log.Debug(Fmt("Added to lastPrecommits: %v", cs.LastPrecommits.StringShort())) } return - case types.VoteTypePrecommit: - // Precommits checks for height+round match. - added, index, err = cs.Precommits.AddByAddress(address, vote) - if added { - log.Debug(Fmt("Added precommit: %v", cs.Precommits.StringShort())) - } - return - case types.VoteTypeCommit: - if vote.Height == cs.Height { - // No need to check if vote.Round < cs.Round ... - // Prevotes && Precommits already checks that. - cs.Prevotes.AddByAddress(address, vote) - cs.Precommits.AddByAddress(address, vote) - added, index, err = cs.Commits.AddByAddress(address, vote) - if added && cs.Commits.HasTwoThirdsMajority() && cs.CommitTime.IsZero() { - cs.CommitTime = time.Now() - log.Debug(Fmt("Set CommitTime to %v", cs.CommitTime)) - if cs.Step < RoundStepCommit { - cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionCommit}) - } else { - cs.queueAction(RoundAction{cs.Height, cs.Round, RoundActionTryFinalize}) - } - } - if added { - log.Debug(Fmt("Added commit: %v\nprecommits: %v\nprevotes: %v", - cs.Commits.StringShort(), - cs.Precommits.StringShort(), - cs.Prevotes.StringShort())) - } - return - } - if vote.Height+1 == cs.Height { - added, index, err = cs.LastCommits.AddByAddress(address, vote) - log.Debug(Fmt("Added lastCommits: %v", cs.LastCommits.StringShort())) - return - } - return false, 0, nil - default: - panic("Unknown vote type") } + + // A prevote/precommit for this height? + if vote.Height == cs.Height { + added, index, err = cs.Votes.AddByAddress(address, vote) + if added { + switch vote.Type { + case types.VoteTypePrevote: + log.Debug(Fmt("Added to prevotes: %v", cs.Votes.Prevotes(vote.Round).StringShort())) + if cs.Round < vote.Round && cs.Votes.Prevotes(vote.Round).HasTwoThirdsAny() { + // Goto to Prevote next round. + go func() { + cs.EnterNewRound(height, vote.Round) + cs.EnterPrevote(height, vote.Round) + }() + } + if cs.Round == vote.Round { + if cs.Votes.Prevotes(cs.Round).HasTwoThirdsMajority() { + // Goto Precommit, whether for block or nil. + go func() { + cs.EnterPrecommit(height, cs.Round) + }() + } + if cs.Votes.Prevotes(cs.Round).HasTwoThirdsAny() { + // Goto PrevoteWait + go func() { + cs.EnterPrevoteWait(height, cs.Round) + }() + } + } + case types.VoteTypePrecommit: + log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort())) + if cs.Round < vote.Round && cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { + // Skip to Precommit next round. + go func() { + cs.EnterNewRound(height, vote.Round) + cs.EnterPrecommit(height, vote.Round) + }() + } + if cs.Round == vote.Round { + if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { + if len(hash) == 0 { + // If hash is nil, goto NewRound + cs.EnterNewRound(height, cs.Round+1) + } else { + // If hash is block, goto Commit + cs.EnterCommit(height, cs.Round) + } + } + if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { + // Goto PrecommitWait + go func() { + cs.EnterPrecommitWait(height, cs.Round) + }() + } + } + default: + panic(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen. + } + } + return + } + + // Height mismatch, bad peer? TODO + return } func (cs *ConsensusState) stageBlock(block *types.Block, blockParts *types.PartSet) error { @@ -1147,34 +1079,12 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.Part } } -// sign a Commit-Vote -func (cs *ConsensusState) commitVoteBlock(block *types.Block, blockParts *types.PartSet) { +// Save Block, save the +2/3 Commits we've seen +func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSet, commits *VoteSet) { // The proposal must be valid. if err := cs.stageBlock(block, blockParts); err != nil { - // Prevent zombies. - log.Warn("commitVoteBlock() an invalid block", "error", err) - return - } - - // Commit-vote. - if cs.lastCommitVoteHeight < block.Height { - cs.signAddVote(types.VoteTypeCommit, block.Hash(), blockParts.Header()) - cs.lastCommitVoteHeight = block.Height - } else { - log.Error("Duplicate commitVoteBlock() attempt", "lastCommitVoteHeight", cs.lastCommitVoteHeight, "types.Height", block.Height) - } -} - -// Save Block, save the +2/3 Commits we've seen, -// and sign a Commit-Vote if we haven't already -func (cs *ConsensusState) saveCommitVoteBlock(block *types.Block, blockParts *types.PartSet, commits *VoteSet) { - - // The proposal must be valid. - if err := cs.stageBlock(block, blockParts); err != nil { - // Prevent zombies. - log.Warn("saveCommitVoteBlock() an invalid block", "error", err) - return + panic(Fmt("saveBlock() an invalid block: %v", err)) } // Save to blockStore. @@ -1189,66 +1099,9 @@ func (cs *ConsensusState) saveCommitVoteBlock(block *types.Block, blockParts *ty // Update mempool. cs.mempoolReactor.Mempool.ResetForBlockAndState(block, cs.stagedState) - // Commit-vote if we haven't already. - if cs.lastCommitVoteHeight < block.Height { - cs.signAddVote(types.VoteTypeCommit, block.Hash(), blockParts.Header()) - cs.lastCommitVoteHeight = block.Height - } } // implements events.Eventable func (cs *ConsensusState) SetFireable(evsw events.Fireable) { cs.evsw = evsw } - -//----------------------------------------------------------------------------- - -// total duration of given round -func calcRoundDuration(round uint) time.Duration { - return RoundDuration0 + RoundDurationDelta*time.Duration(round) -} - -// startTime is when round zero started. -func calcRoundStartTime(round uint, startTime time.Time) time.Time { - return startTime.Add(RoundDuration0*time.Duration(round) + - RoundDurationDelta*(time.Duration((int64(round)*int64(round)-int64(round))/2))) -} - -// calculates the current round given startTime of round zero. -// NOTE: round is zero if startTime is in the future. -func calcRound(startTime time.Time) uint { - now := time.Now() - if now.Before(startTime) { - return 0 - } - // Start + D_0 * R + D_delta * (R^2 - R)/2 <= Now; find largest integer R. - // D_delta * R^2 + (2D_0 - D_delta) * R + 2(Start - Now) <= 0. - // AR^2 + BR + C <= 0; A = D_delta, B = (2_D0 - D_delta), C = 2(Start - Now). - // R = Floor((-B + Sqrt(B^2 - 4AC))/2A) - A := float64(RoundDurationDelta) - B := 2.0*float64(RoundDuration0) - float64(RoundDurationDelta) - C := 2.0 * float64(startTime.Sub(now)) - R := math.Floor((-B + math.Sqrt(B*B-4.0*A*C)) / (2 * A)) - if math.IsNaN(R) { - panic("Could not calc round, should not happen") - } - if R > math.MaxInt32 { - panic(Fmt("Could not calc round, round overflow: %v", R)) - } - if R < 0 { - return 0 - } - return uint(R) -} - -// convenience -// NOTE: elapsedRatio can be negative if startTime is in the future. -func calcRoundInfo(startTime time.Time) (round uint, roundStartTime time.Time, RoundDuration time.Duration, - roundElapsed time.Duration, elapsedRatio float64) { - round = calcRound(startTime) - roundStartTime = calcRoundStartTime(round, startTime) - RoundDuration = calcRoundDuration(round) - roundElapsed = time.Now().Sub(roundStartTime) - elapsedRatio = float64(roundElapsed) / float64(RoundDuration) - return -} diff --git a/consensus/state_test.go b/consensus/state_test.go index af25ae01..48e8bb50 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -8,51 +8,6 @@ import ( "github.com/tendermint/tendermint/types" ) -func TestSetupRound(t *testing.T) { - cs, privValidators := randConsensusState() - val0 := privValidators[0] - - // Add a vote, precommit, and commit by val0. - voteTypes := []byte{types.VoteTypePrevote, types.VoteTypePrecommit, types.VoteTypeCommit} - for _, voteType := range voteTypes { - vote := &types.Vote{Height: 1, Round: 0, Type: voteType} // nil vote - err := val0.SignVote(cs.state.ChainID, vote) - if err != nil { - t.Error("Error signing vote: %v", err) - } - cs.AddVote(val0.Address, vote) - } - - // Ensure that vote appears in RoundState. - rs0 := cs.GetRoundState() - if vote := rs0.Prevotes.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypePrevote { - t.Errorf("Expected to find prevote but got %v", vote) - } - if vote := rs0.Precommits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypePrecommit { - t.Errorf("Expected to find precommit but got %v", vote) - } - if vote := rs0.Commits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit { - t.Errorf("Expected to find commit but got %v", vote) - } - - // Setup round 1 (next round) - cs.SetupNewRound(1, 1) - <-cs.NewStepCh() - - // Now the commit should be copied over to prevotes and precommits. - rs1 := cs.GetRoundState() - if vote := rs1.Prevotes.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit { - t.Errorf("Expected to find commit but got %v", vote) - } - if vote := rs1.Precommits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit { - t.Errorf("Expected to find commit but got %v", vote) - } - if vote := rs1.Commits.GetByAddress(val0.Address); vote == nil || vote.Type != types.VoteTypeCommit { - t.Errorf("Expected to find commit but got %v", vote) - } - -} - func TestRunActionProposeNoPrivValidator(t *testing.T) { cs, _ := randConsensusState() cs.RunActionPropose(1, 0) @@ -82,128 +37,4 @@ func TestRunActionPropose(t *testing.T) { } } -func checkRoundState(t *testing.T, rs *RoundState, - height uint, round uint, step RoundStepType) { - if rs.Height != height { - t.Errorf("rs.Height should be %v, got %v", height, rs.Height) - } - if rs.Round != round { - t.Errorf("rs.Round should be %v, got %v", round, rs.Round) - } - if rs.Step != step { - t.Errorf("rs.Step should be %v, got %v", step, rs.Step) - } -} - -func TestRunActionPrecommitCommitFinalize(t *testing.T) { - cs, privValidators := randConsensusState() - val0 := privValidators[0] - cs.SetPrivValidator(val0) - - cs.RunActionPrecommit(1, 0) - <-cs.NewStepCh() // TODO: test this value too. - if cs.Precommits.GetByAddress(val0.Address) != nil { - t.Errorf("RunActionPrecommit should return nil without a proposal") - } - - cs.RunActionPropose(1, 0) - <-cs.NewStepCh() // TODO: test this value too. - - cs.RunActionPrecommit(1, 0) - <-cs.NewStepCh() // TODO: test this value too. - if cs.Precommits.GetByAddress(val0.Address) != nil { - t.Errorf("RunActionPrecommit should return nil, not enough prevotes") - } - - // Add at least +2/3 prevotes. - for i := 0; i < 7; i++ { - vote := &types.Vote{ - Height: 1, - Round: 0, - Type: types.VoteTypePrevote, - BlockHash: cs.ProposalBlock.Hash(), - BlockParts: cs.ProposalBlockParts.Header(), - } - err := privValidators[i].SignVote(cs.state.ChainID, vote) - if err != nil { - t.Error("Error signing vote: %v", err) - } - cs.AddVote(privValidators[i].Address, vote) - } - - // Test RunActionPrecommit success: - cs.RunActionPrecommit(1, 0) - <-cs.NewStepCh() // TODO: test this value too. - if cs.Precommits.GetByAddress(val0.Address) == nil { - t.Errorf("RunActionPrecommit should have succeeded") - } - checkRoundState(t, cs.GetRoundState(), 1, 0, RoundStepPrecommit) - - // Add at least +2/3 precommits. - for i := 0; i < 7; i++ { - if bytes.Equal(privValidators[i].Address, val0.Address) { - if cs.Precommits.GetByAddress(val0.Address) == nil { - t.Errorf("Proposer should already have signed a precommit vote") - } - continue - } - vote := &types.Vote{ - Height: 1, - Round: 0, - Type: types.VoteTypePrecommit, - BlockHash: cs.ProposalBlock.Hash(), - BlockParts: cs.ProposalBlockParts.Header(), - } - err := privValidators[i].SignVote(cs.state.ChainID, vote) - if err != nil { - t.Error("Error signing vote: %v", err) - } - added, _, err := cs.AddVote(privValidators[i].Address, vote) - if !added || err != nil { - t.Errorf("Error adding precommit: %v", err) - } - } - - // Test RunActionCommit success: - cs.RunActionCommit(1) - <-cs.NewStepCh() // TODO: test this value too. - if cs.Commits.GetByAddress(val0.Address) == nil { - t.Errorf("RunActionCommit should have succeeded") - } - checkRoundState(t, cs.GetRoundState(), 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++ { - if bytes.Equal(privValidators[i].Address, val0.Address) { - if cs.Commits.GetByAddress(val0.Address) == nil { - t.Errorf("Proposer should already have signed a commit vote") - } - continue - } - vote := &types.Vote{ - Height: 1, - Round: uint(i), // Doesn't matter what round - Type: types.VoteTypeCommit, - BlockHash: cs.ProposalBlock.Hash(), - BlockParts: cs.ProposalBlockParts.Header(), - } - err := privValidators[i].SignVote(cs.state.ChainID, vote) - if err != nil { - t.Error("Error signing vote: %v", err) - } - added, _, err := cs.AddVote(privValidators[i].Address, vote) - if !added || err != nil { - t.Errorf("Error adding commit: %v", err) - } - } - - // Test TryFinalizeCommit: - cs.TryFinalizeCommit(1) - <-cs.NewStepCh() // TODO: test this value too. - checkRoundState(t, cs.GetRoundState(), 2, 0, RoundStepNewHeight) -} +// TODO write better consensus state tests diff --git a/consensus/types/proposal.go b/consensus/types/proposal.go index d2fb4b15..5009e681 100644 --- a/consensus/types/proposal.go +++ b/consensus/types/proposal.go @@ -20,29 +20,28 @@ type Proposal struct { Height uint `json:"height"` Round uint `json:"round"` BlockParts types.PartSetHeader `json:"block_parts"` - POLParts types.PartSetHeader `json:"pol_parts"` + POLRound int `json:"pol_round"` // -1 if null. Signature account.SignatureEd25519 `json:"signature"` } -func NewProposal(height uint, round uint, blockParts, polParts types.PartSetHeader) *Proposal { +func NewProposal(height uint, round uint, blockParts types.PartSetHeader, polRound int) *Proposal { return &Proposal{ Height: height, Round: round, BlockParts: blockParts, - POLParts: polParts, + POLRound: polRound, } } func (p *Proposal) String() string { return fmt.Sprintf("Proposal{%v/%v %v %v %v}", p.Height, p.Round, - p.BlockParts, p.POLParts, p.Signature) + p.BlockParts, p.POLRound, p.Signature) } func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) binary.WriteTo([]byte(`,"proposal":{"block_parts":`), w, n, err) p.BlockParts.WriteSignBytes(w, n, err) - binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_parts":`, p.Height)), w, n, err) - p.POLParts.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_round":%v`, p.Height, p.POLRound)), w, n, err) binary.WriteTo([]byte(Fmt(`,"round":%v}}`, p.Round)), w, n, err) } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index 39f7747b..136d1463 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -39,9 +39,6 @@ func NewVoteSet(height uint, round uint, type_ byte, valSet *sm.ValidatorSet) *V if height == 0 { panic("Cannot make VoteSet for height == 0, doesn't make sense.") } - if type_ == types.VoteTypeCommit && round != 0 { - panic("Expected round 0 for commit vote set") - } return &VoteSet{ height: height, round: round, @@ -111,10 +108,9 @@ func (voteSet *VoteSet) addByIndex(valIndex uint, vote *types.Vote) (bool, uint, func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vote) (bool, uint, error) { // Make sure the step matches. (or that vote is commit && round < voteSet.round) - if vote.Height != voteSet.height || - (vote.Type != types.VoteTypeCommit && vote.Round != voteSet.round) || - (vote.Type != types.VoteTypeCommit && vote.Type != voteSet.type_) || - (vote.Type == types.VoteTypeCommit && voteSet.type_ != types.VoteTypeCommit && vote.Round >= voteSet.round) { + if (vote.Height != voteSet.height) || + (vote.Round != voteSet.round) || + (vote.Type != voteSet.type_) { return false, 0, types.ErrVoteUnexpectedStep } @@ -155,18 +151,6 @@ func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vo return true, valIndex, nil } -// Assumes that commits VoteSet is valid. -func (voteSet *VoteSet) AddFromCommits(commits *VoteSet) { - for valIndex, commit := range commits.votes { - if commit == nil { - continue - } - if commit.Round < voteSet.round { - voteSet.addByIndex(uint(valIndex), commit) - } - } -} - func (voteSet *VoteSet) BitArray() *BitArray { if voteSet == nil { return nil @@ -201,6 +185,15 @@ func (voteSet *VoteSet) HasTwoThirdsMajority() bool { return voteSet.maj23Exists } +func (voteSet *VoteSet) HasTwoThirdsAny() bool { + if voteSet == nil { + return false + } + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + return voteSet.totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3 +} + // Returns either a blockhash (or nil) that received +2/3 majority. // If there exists no such majority, returns (nil, false). func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHeader, ok bool) { @@ -213,50 +206,16 @@ func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHea } } -func (voteSet *VoteSet) MakePOL() *POL { - if voteSet.type_ != types.VoteTypePrevote { - panic("Cannot MakePOL() unless VoteSet.Type is types.VoteTypePrevote") - } - voteSet.mtx.Lock() - defer voteSet.mtx.Unlock() - if !voteSet.maj23Exists { - return nil - } - pol := &POL{ - Height: voteSet.height, - Round: voteSet.round, - BlockHash: voteSet.maj23Hash, - BlockParts: voteSet.maj23Parts, - Votes: make([]POLVoteSignature, voteSet.valSet.Size()), - } - for valIndex, vote := range voteSet.votes { - if vote == nil { - continue - } - if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) { - continue - } - if !vote.BlockParts.Equals(voteSet.maj23Parts) { - continue - } - pol.Votes[valIndex] = POLVoteSignature{ - Round: vote.Round, - Signature: vote.Signature, - } - } - return pol -} - func (voteSet *VoteSet) MakeValidation() *types.Validation { - if voteSet.type_ != types.VoteTypeCommit { - panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypeCommit") + if voteSet.type_ != types.VoteTypePrecommit { + panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypePrecommit") } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() if len(voteSet.maj23Hash) == 0 { panic("Cannot MakeValidation() unless a blockhash has +2/3") } - commits := make([]types.Commit, voteSet.valSet.Size()) + precommits := make([]types.Precommit, voteSet.valSet.Size()) voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { vote := voteSet.votes[valIndex] if vote == nil { @@ -268,11 +227,12 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { if !vote.BlockParts.Equals(voteSet.maj23Parts) { return false } - commits[valIndex] = types.Commit{val.Address, vote.Round, vote.Signature} + precommits[valIndex] = types.Precommit{val.Address, vote.Signature} return false }) return &types.Validation{ - Commits: commits, + Round: voteSet.round, + Precommits: precommits, } } diff --git a/consensus/vote_set_test.go b/consensus/vote_set_test.go index 8cc919f3..46bafed6 100644 --- a/consensus/vote_set_test.go +++ b/consensus/vote_set_test.go @@ -225,69 +225,12 @@ func TestBadVotes(t *testing.T) { } } -func TestAddCommitsToPrevoteVotes(t *testing.T) { - height, round := uint(2), uint(5) - voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) - - // val0, val1, val2, val3, val4, val5 vote for nil. - vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: nil} - for i := 0; i < 6; i++ { - signAddVote(privValidators[i], vote, voteSet) - } - hash, header, ok := voteSet.TwoThirdsMajority() - if hash != nil || !header.IsZero() || ok { - t.Errorf("There should be no 2/3 majority") - } - - // Attempt to add a commit from val6 at a previous height - vote = &types.Vote{Height: height - 1, Round: round, Type: types.VoteTypeCommit, BlockHash: nil} - added, _ := signAddVote(privValidators[6], vote, voteSet) - if added { - t.Errorf("Expected VoteSet.Add to fail, wrong height.") - } - - // Attempt to add a commit from val6 at a later round - vote = &types.Vote{Height: height, Round: round + 1, Type: types.VoteTypeCommit, BlockHash: nil} - added, _ = signAddVote(privValidators[6], vote, voteSet) - if added { - t.Errorf("Expected VoteSet.Add to fail, cannot add future round vote.") - } - - // Attempt to add a commit from val6 for currrent height/round. - vote = &types.Vote{Height: height, Round: round, Type: types.VoteTypeCommit, BlockHash: nil} - added, err := signAddVote(privValidators[6], vote, voteSet) - if added || err == nil { - t.Errorf("Expected VoteSet.Add to fail, only prior round commits can be added.") - } - - // Add commit from val6 at a previous round - vote = &types.Vote{Height: height, Round: round - 1, Type: types.VoteTypeCommit, BlockHash: nil} - added, err = signAddVote(privValidators[6], vote, voteSet) - if !added || err != nil { - t.Errorf("Expected VoteSet.Add to succeed, commit for prior rounds are relevant.") - } - - // Also add commit from val7 for previous round. - vote = &types.Vote{Height: height, Round: round - 2, Type: types.VoteTypeCommit, BlockHash: nil} - added, err = signAddVote(privValidators[7], vote, voteSet) - if !added || err != nil { - t.Errorf("Expected VoteSet.Add to succeed. err: %v", err) - } - - // We should have 2/3 majority - hash, header, ok = voteSet.TwoThirdsMajority() - if hash != nil || !header.IsZero() || !ok { - t.Errorf("There should be 2/3 majority for nil") - } - -} - func TestMakeValidation(t *testing.T) { height, round := uint(1), uint(0) - voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypeCommit, 10, 1) + voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrecommit, 10, 1) blockHash, blockParts := CRandBytes(32), types.PartSetHeader{123, CRandBytes(32)} - vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypeCommit, + vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrecommit, BlockHash: blockHash, BlockParts: blockParts} // 6 out of 10 voted for some block. @@ -313,11 +256,11 @@ func TestMakeValidation(t *testing.T) { validation := voteSet.MakeValidation() // Validation should have 10 elements - if len(validation.Commits) != 10 { - t.Errorf("Validation Commits should have the same number of commits as validators") + if len(validation.Precommits) != 10 { + t.Errorf("Validation Precommits should have the same number of precommits as validators") } - // Ensure that Validation commits are ordered. + // Ensure that Validation precommits are ordered. if err := validation.ValidateBasic(); err != nil { t.Errorf("Error in Validation.ValidateBasic(): %v", err) } diff --git a/state/execution.go b/state/execution.go index c2edef8b..1d85c949 100644 --- a/state/execution.go +++ b/state/execution.go @@ -40,28 +40,28 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // Validate block Validation. if block.Height == 1 { - if len(block.Validation.Commits) != 0 { - return errors.New("Block at height 1 (first block) should have no Validation commits") + if len(block.Validation.Precommits) != 0 { + return errors.New("Block at height 1 (first block) should have no Validation precommits") } } else { - if uint(len(block.Validation.Commits)) != s.LastBondedValidators.Size() { + if uint(len(block.Validation.Precommits)) != s.LastBondedValidators.Size() { return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.Validation.Commits))) + s.LastBondedValidators.Size(), len(block.Validation.Precommits))) } var sumVotingPower uint64 s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { - commit := block.Validation.Commits[index] - if commit.IsZero() { + precommit := block.Validation.Precommits[index] + if precommit.IsZero() { return false } else { vote := &types.Vote{ Height: block.Height - 1, - Round: commit.Round, - Type: types.VoteTypeCommit, + Round: block.Validation.Round, + Type: types.VoteTypePrecommit, BlockHash: block.LastBlockHash, BlockParts: block.LastBlockParts, } - if val.PubKey.VerifyBytes(account.SignBytes(s.ChainID, vote), commit.Signature) { + if val.PubKey.VerifyBytes(account.SignBytes(s.ChainID, vote), precommit.Signature) { sumVotingPower += val.VotingPower return false } else { @@ -80,8 +80,8 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade } // Update Validator.LastCommitHeight as necessary. - for i, commit := range block.Validation.Commits { - if commit.IsZero() { + for i, precommit := range block.Validation.Precommits { + if precommit.IsZero() { continue } _, val := s.LastBondedValidators.GetByIndex(uint(i)) @@ -111,7 +111,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // Create BlockCache to cache changes to state. blockCache := NewBlockCache(s) - // Commit each tx + // Execute each tx for _, tx := range block.Data.Txs { err := ExecTx(blockCache, tx, true, s.evc) if err != nil { @@ -726,21 +726,14 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea if tx.VoteA.Height != tx.VoteB.Height { return errors.New("DupeoutTx heights don't match") } - if tx.VoteA.Type == types.VoteTypeCommit && tx.VoteA.Round < tx.VoteB.Round { - // Check special case (not an error, validator must be slashed!) - // Validators should not sign another vote after committing. - } else if tx.VoteB.Type == types.VoteTypeCommit && tx.VoteB.Round < tx.VoteA.Round { - // We need to check both orderings of the votes - } else { - if tx.VoteA.Round != tx.VoteB.Round { - return errors.New("DupeoutTx rounds don't match") - } - if tx.VoteA.Type != tx.VoteB.Type { - return errors.New("DupeoutTx types don't match") - } - if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { - return errors.New("DupeoutTx blockhashes shouldn't match") - } + if tx.VoteA.Round != tx.VoteB.Round { + return errors.New("DupeoutTx rounds don't match") + } + if tx.VoteA.Type != tx.VoteB.Type { + return errors.New("DupeoutTx types don't match") + } + if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { + return errors.New("DupeoutTx blockhashes shouldn't match") } // Good! (Bad validator!) diff --git a/state/priv_validator.go b/state/priv_validator.go index e9ce7be5..41c8b913 100644 --- a/state/priv_validator.go +++ b/state/priv_validator.go @@ -1,7 +1,5 @@ package state -// TODO: This logic is crude. Should be more transactional. - import ( "errors" "fmt" @@ -23,7 +21,6 @@ const ( stepPropose = 1 stepPrevote = 2 stepPrecommit = 3 - stepCommit = 4 ) func voteToStep(vote *types.Vote) uint8 { @@ -32,8 +29,6 @@ func voteToStep(vote *types.Vote) uint8 { return stepPrevote case types.VoteTypePrecommit: return stepPrecommit - case types.VoteTypeCommit: - return stepCommit default: panic("Unknown vote type") } @@ -108,7 +103,6 @@ func (privVal *PrivValidator) save() { } } -// TODO: test func (privVal *PrivValidator) SignVote(chainID string, vote *types.Vote) error { privVal.mtx.Lock() defer privVal.mtx.Unlock() @@ -119,10 +113,6 @@ func (privVal *PrivValidator) SignVote(chainID string, vote *types.Vote) error { } // More cases for when the height matches if privVal.LastHeight == vote.Height { - // If attempting any sign after commit, panic - if privVal.LastStep == stepCommit { - return errors.New("SignVote on matching height after a commit") - } // If round regression, panic if privVal.LastRound > vote.Round { return errors.New("Round regression in SignVote") diff --git a/state/state.go b/state/state.go index fcb40850..e7486b27 100644 --- a/state/state.go +++ b/state/state.go @@ -130,7 +130,7 @@ func (s *State) Hash() []byte { } // Mutates the block in place and updates it with new state hash. -func (s *State) SetBlockStateHash(block *types.Block) error { +func (s *State) ComputeBlockStateHash(block *types.Block) error { sCopy := s.Copy() // sCopy has no event cache in it, so this won't fire events err := execBlock(sCopy, block, types.PartSetHeader{}) diff --git a/state/state_test.go b/state/state_test.go index acb809fb..9e5219dd 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -71,7 +71,7 @@ func TestCopyState(t *testing.T) { } } -func makeBlock(t *testing.T, state *State, commits []types.Commit, txs []types.Tx) *types.Block { +func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []types.Tx) *types.Block { block := &types.Block{ Header: &types.Header{ ChainID: state.ChainID, @@ -83,16 +83,14 @@ func makeBlock(t *testing.T, state *State, commits []types.Commit, txs []types.T LastBlockParts: state.LastBlockParts, StateHash: nil, }, - Validation: &types.Validation{ - Commits: commits, - }, + Validation: validation, Data: &types.Data{ Txs: txs, }, } // Fill in block StateHash - err := state.SetBlockStateHash(block) + err := state.ComputeBlockStateHash(block) if err != nil { t.Error("Error appending initial block:", err) } @@ -620,21 +618,23 @@ func TestAddValidator(t *testing.T) { // The validation for the next block should only require 1 signature // (the new validator wasn't active for block0) - commit0 := &types.Vote{ + precommit0 := &types.Vote{ Height: 1, Round: 0, - Type: types.VoteTypeCommit, + Type: types.VoteTypePrecommit, BlockHash: block0.Hash(), BlockParts: block0Parts.Header(), } - privValidators[0].SignVote(s0.ChainID, commit0) + privValidators[0].SignVote(s0.ChainID, precommit0) block1 := makeBlock(t, s0, - []types.Commit{ - types.Commit{ - Address: privValidators[0].Address, - Round: 0, - Signature: commit0.Signature, + types.Validation{ + Round: 0, + Precommits: []types.Precommit{ + types.Precommit{ + Address: privValidators[0].Address, + Signature: precommit0.Signature, + }, }, }, nil, ) diff --git a/state/validator_set.go b/state/validator_set.go index 5bfc76f9..da5585c0 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -202,33 +202,33 @@ func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { // Verify that +2/3 of the set had signed the given signBytes func (valSet *ValidatorSet) VerifyValidation(chainID string, hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error { - if valSet.Size() != uint(len(v.Commits)) { + if valSet.Size() != uint(len(v.Precommits)) { return errors.New(Fmt("Invalid validation -- wrong set size: %v vs %v", - valSet.Size(), len(v.Commits))) + valSet.Size(), len(v.Precommits))) } talliedVotingPower := uint64(0) seenValidators := map[string]struct{}{} - for idx, commit := range v.Commits { + for idx, precommit := range v.Precommits { // may be zero, in which case skip. - if commit.Signature.IsZero() { + if precommit.Signature.IsZero() { continue } _, val := valSet.GetByIndex(uint(idx)) - commitSignBytes := account.SignBytes(chainID, &types.Vote{ - Height: height, Round: commit.Round, Type: types.VoteTypeCommit, + precommitSignBytes := account.SignBytes(chainID, &types.Vote{ + Height: height, Round: v.Round, Type: types.VoteTypePrecommit, BlockHash: hash, BlockParts: parts, }) // Validate if _, seen := seenValidators[string(val.Address)]; seen { - return fmt.Errorf("Duplicate validator for commit %v for Validation %v", commit, v) + return fmt.Errorf("Duplicate validator for precommit %v for Validation %v", precommit, v) } - if !val.PubKey.VerifyBytes(commitSignBytes, commit.Signature) { - return fmt.Errorf("Invalid signature for commit %v for Validation %v", commit, v) + if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { + return fmt.Errorf("Invalid signature for precommit %v for Validation %v", precommit, v) } // Tally diff --git a/types/block.go b/types/block.go index 8411edad..c42dbeb9 100644 --- a/types/block.go +++ b/types/block.go @@ -176,27 +176,27 @@ func (h *Header) StringIndented(indent string) string { //----------------------------------------------------------------------------- -type Commit struct { +type Precommit struct { Address []byte `json:"address"` - Round uint `json:"round"` Signature account.SignatureEd25519 `json:"signature"` } -func (commit Commit) IsZero() bool { - return commit.Round == 0 && commit.Signature.IsZero() +func (pc Precommit) IsZero() bool { + return pc.Signature.IsZero() } -func (commit Commit) String() string { - return fmt.Sprintf("Commit{A:%X R:%v %X}", commit.Address, commit.Round, Fingerprint(commit.Signature)) +func (pc Precommit) String() string { + return fmt.Sprintf("Precommit{A:%X %X}", pc.Address, Fingerprint(pc.Signature)) } //------------------------------------- -// NOTE: The Commits are in order of address to preserve the bonded ValidatorSet order. -// Any peer with a block can gossip commits by index with a peer without recalculating the +// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. +// Any peer with a block can gossip precommits by index with a peer without recalculating the // active ValidatorSet. type Validation struct { - Commits []Commit `json:"commits"` // Commits (or nil) of all active validators in address order. + Round uint `json:"round"` // Round for all precommits + Precommits []Precommit `json:"precommits"` // Precommits (or nil) of all active validators in address order. // Volatile hash []byte @@ -204,24 +204,24 @@ type Validation struct { } func (v *Validation) ValidateBasic() error { - if len(v.Commits) == 0 { - return errors.New("No commits in validation") + if len(v.Precommits) == 0 { + return errors.New("No precommits in validation") } lastAddress := []byte{} - for i := 0; i < len(v.Commits); i++ { - commit := v.Commits[i] - if commit.IsZero() { - if len(commit.Address) > 0 { - return errors.New("Zero commits should not have an address") + for i := 0; i < len(v.Precommits); i++ { + precommit := v.Precommits[i] + if precommit.IsZero() { + if len(precommit.Address) > 0 { + return errors.New("Zero precommits should not have an address") } } else { - if len(commit.Address) == 0 { - return errors.New("Nonzero commits should have an address") + if len(precommit.Address) == 0 { + return errors.New("Nonzero precommits should have an address") } - if len(lastAddress) > 0 && bytes.Compare(lastAddress, commit.Address) != -1 { - return errors.New("Invalid commit order") + if len(lastAddress) > 0 && bytes.Compare(lastAddress, precommit.Address) != -1 { + return errors.New("Invalid precommit order") } - lastAddress = commit.Address + lastAddress = precommit.Address } } return nil @@ -229,9 +229,10 @@ func (v *Validation) ValidateBasic() error { func (v *Validation) Hash() []byte { if v.hash == nil { - bs := make([]interface{}, len(v.Commits)) - for i, commit := range v.Commits { - bs[i] = commit + bs := make([]interface{}, 1+len(v.Precommits)) + bs[0] = v.Round + for i, precommit := range v.Precommits { + bs[1+i] = precommit } v.hash = merkle.SimpleHashFromBinaries(bs) } @@ -242,22 +243,24 @@ func (v *Validation) StringIndented(indent string) string { if v == nil { return "nil-Validation" } - commitStrings := make([]string, len(v.Commits)) - for i, commit := range v.Commits { - commitStrings[i] = commit.String() + precommitStrings := make([]string, len(v.Precommits)) + for i, precommit := range v.Precommits { + precommitStrings[i] = precommit.String() } return fmt.Sprintf(`Validation{ -%s %v +%s Round: %v +%s Precommits: %v %s}#%X`, - indent, strings.Join(commitStrings, "\n"+indent+" "), + indent, v.Round, + indent, strings.Join(precommitStrings, "\n"+indent+" "), indent, v.hash) } func (v *Validation) BitArray() *BitArray { if v.bitArray == nil { - v.bitArray = NewBitArray(uint(len(v.Commits))) - for i, commit := range v.Commits { - v.bitArray.SetIndex(uint(i), !commit.IsZero()) + v.bitArray = NewBitArray(uint(len(v.Precommits))) + for i, precommit := range v.Precommits { + v.bitArray.SetIndex(uint(i), !precommit.IsZero()) } } return v.bitArray diff --git a/types/vote.go b/types/vote.go index 365b5a9a..2e931c23 100644 --- a/types/vote.go +++ b/types/vote.go @@ -27,8 +27,6 @@ func (err *ErrVoteConflictingSignature) Error() string { } // Represents a prevote, precommit, or commit vote from validators for consensus. -// Commit votes get aggregated into the next block's Validaiton. -// See the whitepaper for details. type Vote struct { Height uint `json:"height"` Round uint `json:"round"` @@ -42,7 +40,6 @@ type Vote struct { const ( VoteTypePrevote = byte(0x01) VoteTypePrecommit = byte(0x02) - VoteTypeCommit = byte(0x03) ) func (vote *Vote) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { @@ -63,8 +60,6 @@ func (vote *Vote) String() string { typeString = "Prevote" case VoteTypePrecommit: typeString = "Precommit" - case VoteTypeCommit: - typeString = "Commit" default: panic("Unknown vote type") } From 15e80c6c283ee7f1b33e26091896c4f7080d997c Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 12 Jun 2015 20:24:08 -0700 Subject: [PATCH 04/24] fixes to state transition upon addVote --- consensus/state.go | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 9dc4dd34..4a034307 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -932,10 +932,8 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty var err error cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block) log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash()) - if cs.Step == RoundStepPropose { - if cs.isProposalComplete() { - go cs.EnterPrevote(height, round) - } + if cs.Step == RoundStepPropose && cs.isProposalComplete() { + go cs.EnterPrevote(height, round) } else if cs.Step == RoundStepCommit { cs.TryFinalizeCommit(height) } @@ -975,21 +973,23 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, go func() { cs.EnterNewRound(height, vote.Round) cs.EnterPrevote(height, vote.Round) + cs.EnterPrevoteWait(height, vote.Round) }() - } - if cs.Round == vote.Round { + } else if cs.Round == vote.Round { if cs.Votes.Prevotes(cs.Round).HasTwoThirdsMajority() { // Goto Precommit, whether for block or nil. - go func() { - cs.EnterPrecommit(height, cs.Round) - }() - } - if cs.Votes.Prevotes(cs.Round).HasTwoThirdsAny() { + go cs.EnterPrecommit(height, cs.Round) + } else if cs.Votes.Prevotes(cs.Round).HasTwoThirdsAny() { // Goto PrevoteWait go func() { + cs.EnterPrevote(height, cs.Round) cs.EnterPrevoteWait(height, cs.Round) }() } + } else if cs.Proposal != nil && cs.Proposal.POLRound == vote.Round { + if cs.isProposalComplete() { + go cs.EnterPrevote(height, cs.Round) + } } case types.VoteTypePrecommit: log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort())) @@ -998,21 +998,21 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, go func() { cs.EnterNewRound(height, vote.Round) cs.EnterPrecommit(height, vote.Round) + cs.EnterPrecommitWait(height, vote.Round) }() - } - if cs.Round == vote.Round { + } else if cs.Round == vote.Round { if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { if len(hash) == 0 { // If hash is nil, goto NewRound - cs.EnterNewRound(height, cs.Round+1) + go cs.EnterNewRound(height, cs.Round+1) } else { // If hash is block, goto Commit - cs.EnterCommit(height, cs.Round) + go cs.EnterCommit(height, cs.Round) } - } - if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { + } else if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { // Goto PrecommitWait go func() { + cs.EnterPrecommit(height, cs.Round) cs.EnterPrecommitWait(height, cs.Round) }() } From 927823140bea2118c0c979b70eb4116de9b7edc3 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 14 Jun 2015 11:01:20 -0500 Subject: [PATCH 05/24] fast-forward when +2/3 precommits found for future round --- consensus/height_vote_set.go | 71 +++++++++++++++++++++++++++++------- consensus/state.go | 35 ++++++++++++++---- 2 files changed, 84 insertions(+), 22 deletions(-) diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index dbcc112f..b345c08b 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -14,21 +14,33 @@ type RoundVoteSet struct { Precommits *VoteSet } -// Keeps track of VoteSets for all the rounds of a height. +/* +Keeps track of all VoteSets from round 0 to round 'round'. + +Also keeps track of up to one RoundVoteSet greater than +'round' from each peer, to facilitate fast-forward syncing. +A commit is +2/3 precommits for a block at a round, +but which round is not known in advance, so when a peer +provides a precommit for a round greater than mtx.round, +we create a new entry in roundVoteSets but also remember the +peer to prevent abuse. +*/ type HeightVoteSet struct { height uint valSet *sm.ValidatorSet - mtx sync.Mutex - round uint // max tracked round - roundVoteSets map[uint]RoundVoteSet // keys: [0...round] + mtx sync.Mutex + round uint // max tracked round + roundVoteSets map[uint]RoundVoteSet // keys: [0...round] + peerFastForward map[string]uint // keys: peer.Key; values: round } func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { hvs := &HeightVoteSet{ - height: height, - valSet: valSet, - roundVoteSets: make(map[uint]RoundVoteSet), + height: height, + valSet: valSet, + roundVoteSets: make(map[uint]RoundVoteSet), + peerFFPrevotes: make(map[string]*VoteSet), } hvs.SetRound(0) return hvs @@ -52,22 +64,41 @@ func (hvs *HeightVoteSet) SetRound(round uint) { panic("SetRound() must increment hvs.round") } for r := hvs.round + 1; r <= round; r++ { - prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) - precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) - hvs.roundVoteSets[r] = RoundVoteSet{ - Prevotes: prevotes, - Precommits: precommits, + if _, ok := hvs.roundVoteSet[r]; ok { + continue // Already exists because peerFastForward. } + hvs.addRound(round) } hvs.round = round } +func (hvs *HeightVoteSet) addRound(round uint) { + if _, ok := hvs.roundVoteSet[r]; ok { + panic("addRound() for an existing round") + } + prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) + precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) + hvs.roundVoteSets[r] = RoundVoteSet{ + Prevotes: prevotes, + Precommits: precommits, + } +} + // CONTRACT: if err == nil, added == true -func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peer string) (added bool, index uint, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { + if _, ok := hvs.peerFastForward[peer]; !ok { + hvs.addRound(vote.Round) + hvs.peerFastForwards[peer] = vote.Round + } else { + // Peer has sent a vote that does not match our round, + // for more than one round. Bad peer! + // TODO punish peer. + log.Warn("Deal with peer giving votes from unwanted rounds") + } return } added, index, err = voteSet.AddByAddress(address, vote) @@ -119,16 +150,28 @@ func (hvs *HeightVoteSet) String() string { } func (hvs *HeightVoteSet) StringIndented(indent string) string { - vsStrings := make([]string, 0, hvs.round*2) + vsStrings := make([]string, 0, (len(hvs.roundVoteSets)+1)*2) + // rounds 0 ~ hvs.round inclusive for round := uint(0); round <= hvs.round; round++ { voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() vsStrings = append(vsStrings, voteSetString) voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() vsStrings = append(vsStrings, voteSetString) } + // all other peer fast-forward rounds + for round, roundVoteSet := range hvs.roundVoteSets { + if round <= hvs.round { + continue + } + voteSetString := roundVoteSet.Prevotes.StringShort() + vsStrings = append(vsStrings, voteSetString) + voteSetString = roundVoteSet.Precommits.StringShort() + vsStrings = append(vsStrings, voteSetString) + } return Fmt(`HeightVoteSet{H:%v R:0~%v %s %v %s}`, + hvs.height, hvs.round, indent, strings.Join(vsStrings, "\n"+indent+" "), indent) } diff --git a/consensus/state.go b/consensus/state.go index 4a034307..e29dfd57 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -969,7 +969,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, case types.VoteTypePrevote: log.Debug(Fmt("Added to prevotes: %v", cs.Votes.Prevotes(vote.Round).StringShort())) if cs.Round < vote.Round && cs.Votes.Prevotes(vote.Round).HasTwoThirdsAny() { - // Goto to Prevote next round. + // Goto to Prevote vote.Round. go func() { cs.EnterNewRound(height, vote.Round) cs.EnterPrevote(height, vote.Round) @@ -993,13 +993,32 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, } case types.VoteTypePrecommit: log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort())) - if cs.Round < vote.Round && cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { - // Skip to Precommit next round. - go func() { - cs.EnterNewRound(height, vote.Round) - cs.EnterPrecommit(height, vote.Round) - cs.EnterPrecommitWait(height, vote.Round) - }() + if cs.Round < vote.Round { + if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { + if len(hash) == 0 { + // This is weird, shouldn't happen + log.Warn("This is weird, why did we receive +2/3 of nil precommits?") + // Skip to Precommit of vote.Round + go func() { + cs.EnterNewRound(height, vote.Round) + cs.EnterPrecommit(height, vote.Round) + cs.EnterPrecommitWait(height, vote.Round) + }() + } else { + // If hash is block, goto Commit + go func() { + cs.EnterNewRound(height, vote.Round) + cs.EnterCommit(height, vote.Round) + }() + } + } else if cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { + // Skip to Precommit of vote.Round + go func() { + cs.EnterNewRound(height, vote.Round) + cs.EnterPrecommit(height, vote.Round) + cs.EnterPrecommitWait(height, vote.Round) + }() + } } else if cs.Round == vote.Round { if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { if len(hash) == 0 { From 7752405945aff46faea980873412bf7eedac02d3 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 19 Jun 2015 15:30:10 -0700 Subject: [PATCH 06/24] Draft of consensus/reactor refactor. --- binary/int.go | 7 +- blockchain/store.go | 9 +- consensus/reactor.go | 264 +++++++++++++++++++++--------------------- consensus/state.go | 38 +++--- consensus/vote_set.go | 91 ++++++++++----- types/block.go | 75 +++++------- 6 files changed, 247 insertions(+), 237 deletions(-) diff --git a/binary/int.go b/binary/int.go index 90f3f432..d35c82bf 100644 --- a/binary/int.go +++ b/binary/int.go @@ -158,8 +158,7 @@ func ReadUint64(r io.Reader, n *int64, err *error) uint64 { // Varint -func uvarintSize(i_ uint) int { - i := uint64(i_) +func uvarintSize(i uint64) int { if i == 0 { return 0 } @@ -193,7 +192,7 @@ func WriteVarint(i int, w io.Writer, n *int64, err *error) { negate = true i = -i } - var size = uvarintSize(uint(i)) + var size = uvarintSize(uint64(i)) if negate { // e.g. 0xF1 for a single negative byte WriteUint8(uint8(size+0xF0), w, n, err) @@ -236,7 +235,7 @@ func ReadVarint(r io.Reader, n *int64, err *error) int { // Uvarint func WriteUvarint(i uint, w io.Writer, n *int64, err *error) { - var size = uvarintSize(i) + var size = uvarintSize(uint64(i)) WriteUint8(uint8(size), w, n, err) if size > 0 { buf := make([]byte, 8) diff --git a/blockchain/store.go b/blockchain/store.go index f0f4e77f..67f6ee96 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -101,9 +101,8 @@ func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta { return meta } -// NOTE: the Precommit-vote heights are for the block at `height-1` -// Since these are included in the subsequent block, the height -// is off by 1. +// The +2/3 and other Precommit-votes for block at `height`. +// This Validation comes from block.LastValidation for `height+1`. func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation { var n int64 var err error @@ -158,8 +157,8 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s } // Save block validation (duplicate and separate from the Block) - blockValidationBytes := binary.BinaryBytes(block.Validation) - bs.db.Set(calcBlockValidationKey(height), blockValidationBytes) + blockValidationBytes := binary.BinaryBytes(block.LastValidation) + bs.db.Set(calcBlockValidationKey(height-1), blockValidationBytes) // Save seen validation (seen +2/3 precommits for block) seenValidationBytes := binary.BinaryBytes(seenValidation) diff --git a/consensus/reactor.go b/consensus/reactor.go index 8cae3cbb..d0ba250b 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -49,8 +49,8 @@ type ConsensusReactor struct { func NewConsensusReactor(consensusState *ConsensusState, blockStore *bc.BlockStore, sync bool) *ConsensusReactor { conR := &ConsensusReactor{ - blockStore: blockStore, quit: make(chan struct{}), + blockStore: blockStore, conS: consensusState, sync: sync, } @@ -119,7 +119,7 @@ func (conR *ConsensusReactor) AddPeer(peer *p2p.Peer) { go conR.gossipVotesRoutine(peer, peerState) // Send our state to peer. - conR.sendNewRoundStep(peer) + conR.sendNewRoundStepMessage(peer) } // Implements Reactor @@ -164,18 +164,13 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte case *ProposalMessage: ps.SetHasProposal(msg.Proposal) err = conR.conS.SetProposal(msg.Proposal) - case *PartMessage: if msg.Type == partTypeProposalBlock { ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Proof.Index) _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Round, msg.Part) - } else if msg.Type == partTypeProposalPOL { - ps.SetHasProposalPOLPart(msg.Height, msg.Round, msg.Part.Proof.Index) - _, err = conR.conS.AddProposalPOLPart(msg.Height, msg.Round, msg.Part) } else { log.Warn(Fmt("Unknown part type %v", msg.Type)) } - default: log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) } @@ -186,15 +181,14 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte vote := msg.Vote if rs.Height != vote.Height { if rs.Height == vote.Height+1 { - if rs.Step == RoundStepNewHeight && vote.Type == types.VoteTypeCommit { + if rs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit { goto VOTE_PASS // *ducks* } } return // Wrong height. Not necessarily a bad peer. } VOTE_PASS: - validatorIndex := msg.ValidatorIndex - address, _ := rs.Validators.GetByIndex(validatorIndex) + address, _ := rs.Validators.GetByIndex(msg.ValidatorIndex) added, index, err := conR.conS.AddVote(address, vote) if err != nil { // If conflicting sig, broadcast evidence tx for slashing. Else punish peer. @@ -213,8 +207,8 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte // TODO: punish peer } } - // Initialize Prevotes/Precommits/Commits if needed - ps.EnsureVoteBitArrays(rs.Height, rs.Validators.Size()) + // Initialize Prevotes/Precommits if needed + ps.EnsureVoteBitArrays(rs.Height, rs.Validators.Size(), _) ps.SetHasVote(vote, index) if added { msg := &HasVoteMessage{ @@ -247,7 +241,6 @@ func (conR *ConsensusReactor) SetPrivValidator(priv *sm.PrivValidator) { // reset the state, turn off fast sync, start the consensus-state-machine func (conR *ConsensusReactor) SwitchToConsensus(state *sm.State) { conR.conS.updateToState(state, false) - conR.conS.newStepCh <- conR.conS.getRoundState() conR.sync = false conR.conS.Start() } @@ -270,6 +263,7 @@ func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg * Round: rs.Round, Step: rs.Step, SecondsSinceStartTime: uint(timeElapsed.Seconds()), + LastCommitRound: rs.LastCommit.Round(), } // If the step is commit, then also broadcast a CommitStepMessage. @@ -306,7 +300,7 @@ func (conR *ConsensusReactor) broadcastNewRoundStepRoutine() { } } -func (conR *ConsensusReactor) sendNewRoundStep(peer *p2p.Peer) { +func (conR *ConsensusReactor) sendNewRoundStepMessage(peer *p2p.Peer) { rs := conR.conS.GetRoundState() nrsMsg, csMsg := makeRoundStepMessages(rs) if nrsMsg != nil { @@ -330,8 +324,6 @@ OUTER_LOOP: prs := ps.GetRoundState() // Send proposal Block parts? - // NOTE: if we or peer is at RoundStepCommit*, the round - // won't necessarily match, but that's OK. if rs.ProposalBlockParts.HasHeader(prs.ProposalBlockParts) { //log.Debug("ProposalBlockParts matched", "blockParts", prs.ProposalBlockParts) if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockBitArray.Copy()).PickRandom(); ok { @@ -400,21 +392,6 @@ OUTER_LOOP: continue OUTER_LOOP } - // Send proposal POL parts? - if rs.ProposalPOLParts.HasHeader(prs.ProposalPOLParts) { - if index, ok := rs.ProposalPOLParts.BitArray().Sub(prs.ProposalPOLBitArray.Copy()).PickRandom(); ok { - msg := &PartMessage{ - Height: rs.Height, - Round: rs.Round, - Type: partTypeProposalPOL, - Part: rs.ProposalPOLParts.GetPart(index), - } - peer.Send(DataChannel, msg) - ps.SetHasProposalPOLPart(rs.Height, rs.Round, index) - continue OUTER_LOOP - } - } - // Nothing to do. Sleep. time.Sleep(peerGossipSleepDuration) continue OUTER_LOOP @@ -443,18 +420,23 @@ OUTER_LOOP: sleeping = 0 } + // prsVoteSet: a pointer to a VoteSet field of prs. // Returns true when useful work was done. - trySendVote := func(height uint, voteSet *VoteSet, peerVoteSet *BitArray) (sent bool) { + trySendVote := func(voteSet *VoteSet, prsVoteSet **BitArray) (sent bool) { if voteSet == nil { return false - } else if peerVoteSet == nil { - ps.EnsureVoteBitArrays(height, voteSet.Size()) - return true + } + if *prsVoteSet == nil { + ps.EnsureVoteBitArrays(voteSet.Height(), voteSet.Size(), prs) + // We could return true here (useful work was done) + // or, we can continue since prsVoteSet is no longer nil. + if *prsVoteSet == nil { + panic("prsVoteSet should not be nil after ps.EnsureVoteBitArrays") + } } // TODO: give priority to our vote. - if index, ok := voteSet.BitArray().Sub(peerVoteSet.Copy()).PickRandom(); ok { + if index, ok := voteSet.BitArray().Sub((*prsVoteSet).Copy()).PickRandom(); ok { vote := voteSet.GetByIndex(index) - // NOTE: vote may be a commit. msg := &VoteMessage{index, vote} peer.Send(VoteChannel, msg) ps.SetHasVote(vote, index) @@ -463,93 +445,86 @@ OUTER_LOOP: return false } + // prsVoteSet: a pointer to a VoteSet field of prs. // Returns true when useful work was done. - trySendCommitFromValidation := func(blockMeta *types.BlockMeta, validation *types.Validation, peerVoteSet *BitArray) (sent bool) { + trySendPrecommitFromValidation := func(validation *types.Validation, prsVoteSet **BitArray) (sent bool) { if validation == nil { return false - } else if peerVoteSet == nil { - ps.EnsureVoteBitArrays(blockMeta.Header.Height, uint(len(validation.Commits))) - return true - } - if index, ok := validation.BitArray().Sub(prs.Commits.Copy()).PickRandom(); ok { - commit := validation.Commits[index] - log.Debug("Picked commit to send", "index", index, "commit", commit) - // Reconstruct vote. - vote := &types.Vote{ - Height: prs.Height, - Round: commit.Round, - Type: types.VoteTypeCommit, - BlockHash: blockMeta.Hash, - BlockParts: blockMeta.Parts, - Signature: commit.Signature, + } else if *prsVoteSet == nil { + ps.EnsureVoteBitArrays(validation.Height(), uint(len(validation.Precommits)), prs) + // We could return true here (useful work was done) + // or, we can continue since prsVoteSet is no longer nil. + if *prsVoteSet == nil { + panic("prsVoteSet should not be nil after ps.EnsureVoteBitArrays") } - msg := &VoteMessage{index, vote} + } + if index, ok := validation.BitArray().Sub((*prsVoteSet).Copy()).PickRandom(); ok { + precommit := validation.Precommits[index] + log.Debug("Picked precommit to send", "index", index, "precommit", precommit) + msg := &VoteMessage{index, precommit} peer.Send(VoteChannel, msg) - ps.SetHasVote(vote, index) + ps.SetHasVote(precommit, index) return true } return false } - // If height matches, then send LastCommits, Prevotes, Precommits, or Commits. + // If height matches, then send LastCommit, Prevotes, Precommits. if rs.Height == prs.Height { - - // If there are lastcommits to send... - if prs.Round == 0 && prs.Step == RoundStepNewHeight { - if trySendVote(rs.Height-1, rs.LastCommits, prs.LastCommits) { + // If there are lastCommits to send... + if prs.Step == RoundStepNewHeight { + if trySendVote(rs.LastCommit, prs.LastCommit) { continue OUTER_LOOP } } - // If there are prevotes to send... if rs.Round == prs.Round && prs.Step <= RoundStepPrevote { - if trySendVote(rs.Height, rs.Prevotes, prs.Prevotes) { + if trySendVote(rs.Prevotes, prs.Prevotes) { continue OUTER_LOOP } } - // If there are precommits to send... if rs.Round == prs.Round && prs.Step <= RoundStepPrecommit { - if trySendVote(rs.Height, rs.Precommits, prs.Precommits) { + if trySendVote(rs.Precommits, prs.Precommits) { continue OUTER_LOOP } } + } - // If there are any commits to send... - if trySendVote(rs.Height, rs.Commits, prs.Commits) { - continue OUTER_LOOP + // Special catchup logic. + // If peer is lagging by height 1, send LastCommit. + if prs.Height != 0 && prs.Height == rs.Height-1 { + if prs.Round == rs.LastCommit.Round() { + if trySendVote(rs.LastCommit, prs.Precommits) { + continue OUTER_LOOP + // XXX CONTONUE + } + } else { + ps.SetCatchupCommitRound(prs.Height, rs.LastCommit.Round()) + ps.EnsureVoteBitArrays(prs.Height, rs.LastCommit.Size(), prs) + if trySendVote(rs.LastCommit, prs.CatchupCommit) { + continue OUTER_LOOP + } } } // Catchup logic - if prs.Height != 0 && !prs.HasAllCatchupCommits { - - // If peer is lagging by height 1 - if rs.Height == prs.Height+1 { - if rs.LastCommits.Size() > 0 { - // Sync peer to rs.LastCommits - if trySendVote(prs.Height, rs.LastCommits, prs.Commits) { - continue OUTER_LOOP - } else { - ps.SetHasAllCatchupCommits(prs.Height) - } - } - } - - // If peer is lagging by more than 1, send Validation. - if rs.Height >= prs.Height+2 { - // Load the blockMeta for block at prs.Height - blockMeta := conR.blockStore.LoadBlockMeta(prs.Height) - // Load the block validation for prs.Height+1, - // which contains commit signatures for prs.Height. - validation := conR.blockStore.LoadBlockValidation(prs.Height + 1) - log.Debug("Loaded BlockValidation for catch-up", "height", prs.Height+1, "blockMeta", blockMeta, "validation", validation) - - if trySendCommitFromValidation(blockMeta, validation, prs.Commits) { - continue OUTER_LOOP - } else { - ps.SetHasAllCatchupCommits(prs.Height) - } + // If peer is lagging by more than 1, send Validation. + if prs.Height != 0 && prs.Height <= rs.Height-2 { + // Load the block validation for prs.Height, + // which contains precommit signatures for prs.Height. + validation := conR.blockStore.LoadBlockValidation(prs.Height) + log.Debug("Loaded BlockValidation for catch-up", "height", prs.Height, "validation", validation) + // Peer's CommitRound should be -1 or equal to the validation's precommit rounds. + // If not, warn. + if prs.CommitRound == -1 { + ps.SetCommitRound(prs.Height, validation.Round()) + continue OUTER_LOOP // Get prs := ps.GetRoundState() again. + } else if prs.CommitRound != validation.Round() { + log.Warn("Peer's CommitRound during catchup not equal to commit round", + "height", prs.Height, "validation", validation, "prs.CommitRound", prs.CommitRound) + } else if trySendPrecommitFromValidation(validation, prs.Commit) { + continue OUTER_LOOP } } @@ -558,8 +533,7 @@ OUTER_LOOP: sleeping = 1 log.Debug("No votes to send, sleeping", "peer", peer, "localPV", rs.Prevotes.BitArray(), "peerPV", prs.Prevotes, - "localPC", rs.Precommits.BitArray(), "peerPC", prs.Precommits, - "localCM", rs.Commits.BitArray(), "peerCM", prs.Commits) + "localPC", rs.Precommits.BitArray(), "peerPC", prs.Precommits) } else if sleeping == 2 { // Continued sleep... sleeping = 1 @@ -585,9 +559,13 @@ type PeerRoundState struct { ProposalPOLBitArray *BitArray // True bit -> has part Prevotes *BitArray // All votes peer has for this round Precommits *BitArray // All precommits peer has for this round - Commits *BitArray // All commits peer has for this height - LastCommits *BitArray // All commits peer has for last height - HasAllCatchupCommits bool // Used for catch-up + LastCommitRound uint // Round of commit for last height. + LastCommit *BitArray // All commit precommits of commit for last height. + + // If peer is leading in height, the round that peer believes commit round is. + // If peer is lagging in height, the round that we believe commit round is. + CatchupCommitRound int + CatchupCommit *BitArray // All commit precommits peer has for this height } //----------------------------------------------------------------------------- @@ -658,7 +636,8 @@ func (ps *PeerState) SetHasProposalPOLPart(height uint, round uint, index uint) ps.ProposalPOLBitArray.SetIndex(uint(index), true) } -func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint) { +// prs: If given, will also update this PeerRoundState copy. +func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *PeerRoundState) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -669,14 +648,22 @@ func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint) { if ps.Precommits == nil { ps.Precommits = NewBitArray(numValidators) } - if ps.Commits == nil { - ps.Commits = NewBitArray(numValidators) + if ps.CatchupCommit == nil { + ps.CatchupCommit = NewBitArray(numValidators) } } else if ps.Height == height+1 { - if ps.LastCommits == nil { - ps.LastCommits = NewBitArray(numValidators) + if ps.LastCommit == nil { + ps.LastCommit = NewBitArray(numValidators) } } + + // Also, update prs if given. + if prs != nil { + prs.Prevotes = ps.Prevotes + prs.Precommits = ps.Precommits + prs.LastCommit = ps.LastCommit + prs.CatchupCommit = ps.CatchupCommit + } } func (ps *PeerState) SetHasVote(vote *types.Vote, index uint) { @@ -687,10 +674,10 @@ func (ps *PeerState) SetHasVote(vote *types.Vote, index uint) { } func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) { - if ps.Height == height+1 && type_ == types.VoteTypeCommit { - // Special case for LastCommits. - ps.LastCommits.SetIndex(index, true) - log.Debug("SetHasVote", "lastCommits", ps.LastCommits, "index", index) + if ps.Height == height+1 && ps.LastCommitRound == round && type_ == types.VoteTypePrecommit { + // Special case for LastCommit. + ps.LastCommit.SetIndex(index, true) + log.Debug("setHasVote", "LastCommit", ps.LastCommit, "index", index) return } else if ps.Height != height { // Does not apply. @@ -702,29 +689,33 @@ func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) ps.Prevotes.SetIndex(index, true) log.Debug("SetHasVote", "peer", ps.Key, "prevotes", ps.Prevotes, "index", index) case types.VoteTypePrecommit: + if ps.CommitRound == round { + ps.Commit.SetIndex(index, true) + } ps.Precommits.SetIndex(index, true) log.Debug("SetHasVote", "peer", ps.Key, "precommits", ps.Precommits, "index", index) - case types.VoteTypeCommit: - if round < ps.Round { - ps.Prevotes.SetIndex(index, true) - ps.Precommits.SetIndex(index, true) - } - ps.Commits.SetIndex(index, true) - log.Debug("SetHasVote", "peer", ps.Key, "commits", ps.Commits, "index", index) default: panic("Invalid vote type") } } -// When catching up, this helps keep track of whether -// we should send more commit votes from the block (validation) store -func (ps *PeerState) SetHasAllCatchupCommits(height uint) { +func (ps *PeerState) SetCatchupCommitRound(height, round uint) { ps.mtx.Lock() defer ps.mtx.Unlock() - if ps.Height == height { - ps.HasAllCatchupCommits = true + if ps.Height != height { + return } + if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { + log.Warn("Conflicting CatchupCommitRound", + "height", height, + "orig", ps.CatchupCommitRound, + "new", round, + ) + // TODO think harder + } + ps.CatchupCommitRound = round + ps.CatchupCommit = nil } func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *RoundState) { @@ -735,6 +726,8 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun psHeight := ps.Height psRound := ps.Round //psStep := ps.Step + psCatchupCommitRound := ps.CatchupCommitRound + psCatchupCommit := ps.CatchupCommitRound startTime := time.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second) ps.Height = msg.Height @@ -751,16 +744,22 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun ps.Prevotes = nil ps.Precommits = nil } + if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound { + // Peer caught up to CatchupCommitRound. + ps.Precommits = psCatchupCommit + } if psHeight != msg.Height { - // Shift Commits to LastCommits - if psHeight+1 == msg.Height { - ps.LastCommits = ps.Commits + // Shift Precommits to LastCommit. + if psHeight+1 == msg.Height && psRound == msg.LastCommitRound { + ps.LastCommitRound = msg.LastCommitRound + ps.LastCommit = ps.Precommits } else { - ps.LastCommits = nil + ps.LastCommitRound = msg.LastCommitRound + ps.LastCommit = nil } // We'll update the BitArray capacity later. - ps.Commits = nil - ps.HasAllCatchupCommits = false + ps.CatchupCommitRound = -1 + ps.CatchupCommit = nil } } @@ -780,11 +779,7 @@ func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) { ps.mtx.Lock() defer ps.mtx.Unlock() - // Special case for LastCommits - if ps.Height == msg.Height+1 && msg.Type == types.VoteTypeCommit { - ps.LastCommits.SetIndex(msg.Index, true) - return - } else if ps.Height != msg.Height { + if ps.Height != msg.Height { return } @@ -826,15 +821,18 @@ func DecodeMessage(bz []byte) (msgType byte, msg ConsensusMessage, err error) { //------------------------------------- +// For every height/round/step transition type NewRoundStepMessage struct { Height uint Round uint Step RoundStepType SecondsSinceStartTime uint + LastCommitRound uint } func (m *NewRoundStepMessage) String() string { - return fmt.Sprintf("[NewRoundStep H:%v R:%v S:%v]", m.Height, m.Round, m.Step) + return fmt.Sprintf("[NewRoundStep H:%v R:%v S:%v LCR:%v]", + m.Height, m.Round, m.Step, m.LastCommitRound) } //------------------------------------- diff --git a/consensus/state.go b/consensus/state.go index e29dfd57..4c4858aa 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -56,7 +56,7 @@ Consensus State Machine Overview: * NewHeight: * Upon entering NewHeight, - * Move Precommits to LastPrecommits and increment height. + * Move Precommits to LastCommit and increment height. * Wait until `CommitTime+timeoutCommit` to receive straggler commits. --> Then, goto NewRound round 0 * Proof of Safety: @@ -184,7 +184,7 @@ type RoundState struct { LockedBlock *types.Block LockedBlockParts *types.PartSet Votes *HeightVoteSet - LastPrecommits *VoteSet // Last precommits for Height-1 + LastCommit *VoteSet // Last precommits for Height-1 } func (rs *RoundState) String() string { @@ -201,7 +201,7 @@ func (rs *RoundState) StringIndented(indent string) string { %s ProposalBlock: %v %v %s LockedBlock: %v %v %s Votes: %v -%s LastPrecommits: %v +%s LastCommit: %v %s}`, indent, rs.Height, rs.Round, rs.Step, indent, rs.StartTime, @@ -211,7 +211,7 @@ func (rs *RoundState) StringIndented(indent string) string { indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), indent, rs.Votes.StringIndented(indent+" "), - indent, rs.LastPrecommits.StringShort(), + indent, rs.LastCommit.StringShort(), indent) } @@ -252,13 +252,13 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto } cs.updateToState(state, true) cs.maybeRebond() - cs.reconstructLastPrecommits(state) + cs.reconstructLastCommit(state) return cs } -// Reconstruct LastPrecommits from SeenValidation, which we saved along with the block, +// Reconstruct LastCommit from SeenValidation, which we saved along with the block, // (which happens even before saving the state) -func (cs *ConsensusState) reconstructLastPrecommits(state *sm.State) { +func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { if state.LastBlockHeight == 0 { return } @@ -275,13 +275,13 @@ func (cs *ConsensusState) reconstructLastPrecommits(state *sm.State) { } added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) if !added || err != nil { - panic(Fmt("Failed to reconstruct LastPrecommits: %v", err)) + panic(Fmt("Failed to reconstruct LastCommit: %v", err)) } } if !lastPrecommits.HasTwoThirdsMajority() { - panic("Failed to reconstruct LastPrecommits: Does not have +2/3 maj") + panic("Failed to reconstruct LastCommit: Does not have +2/3 maj") } - cs.LastPrecommits = lastPrecommits + cs.LastCommit = lastPrecommits } func (cs *ConsensusState) GetState() *sm.State { @@ -373,11 +373,14 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { cs.LockedBlock = nil cs.LockedBlockParts = nil cs.Votes = NewHeightVoteSet(height, validators) - cs.LastPrecommits = lastPrecommits + cs.LastCommit = lastPrecommits cs.state = state cs.stagedBlock = nil cs.stagedState = nil + + // Finally, broadcast RoundState + cs.newStepCh <- cs.getRoundState() } // If we're unbonded, broadcast RebondTx. @@ -528,11 +531,11 @@ func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { var validation *types.Validation if cs.Height == 1 { // We're creating a proposal for the first block. - // The validation is empty. + // The validation is empty, but not nil. validation = &types.Validation{} - } else if cs.LastPrecommits.HasTwoThirdsMajority() { - // Make the validation from LastPrecommits - validation = cs.LastPrecommits.MakeValidation() + } else if cs.LastCommit.HasTwoThirdsMajority() { + // Make the validation from LastCommit + validation = cs.LastCommit.MakeValidation() } else { // This shouldn't happen. log.Error("EnterPropose: Cannot propose anything: No validation for the previous block.") @@ -871,7 +874,6 @@ func (cs *ConsensusState) FinalizeCommit(height uint) { // * cs.Height has been increment to height+1 // * cs.Step is now RoundStepNewHeight // * cs.StartTime is set to when we should start round0. - cs.newStepCh <- cs.getRoundState() // Start round 0 when cs.StartTime. go cs.scheduleRound0(height) return @@ -954,9 +956,9 @@ func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote) (added bool, func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { // A precommit for the previous height? if vote.Height+1 == cs.Height && vote.Type == types.VoteTypePrecommit { - added, index, err = cs.LastPrecommits.AddByAddress(address, vote) + added, index, err = cs.LastCommit.AddByAddress(address, vote) if added { - log.Debug(Fmt("Added to lastPrecommits: %v", cs.LastPrecommits.StringShort())) + log.Debug(Fmt("Added to lastPrecommits: %v", cs.LastCommit.StringShort())) } return } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index 136d1463..bcb8fbfa 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -59,6 +59,14 @@ func (voteSet *VoteSet) Height() uint { } } +func (voteSet *VoteSet) Round() uint { + if voteSet == nil { + return 0 + } else { + return voteSet.round + } +} + func (voteSet *VoteSet) Size() uint { if voteSet == nil { return 0 @@ -206,36 +214,6 @@ func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHea } } -func (voteSet *VoteSet) MakeValidation() *types.Validation { - if voteSet.type_ != types.VoteTypePrecommit { - panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypePrecommit") - } - voteSet.mtx.Lock() - defer voteSet.mtx.Unlock() - if len(voteSet.maj23Hash) == 0 { - panic("Cannot MakeValidation() unless a blockhash has +2/3") - } - precommits := make([]types.Precommit, voteSet.valSet.Size()) - voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { - vote := voteSet.votes[valIndex] - if vote == nil { - return false - } - if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) { - return false - } - if !vote.BlockParts.Equals(voteSet.maj23Parts) { - return false - } - precommits[valIndex] = types.Precommit{val.Address, vote.Signature} - return false - }) - return &types.Validation{ - Round: voteSet.round, - Precommits: precommits, - } -} - func (voteSet *VoteSet) String() string { return voteSet.StringIndented("") } @@ -269,3 +247,56 @@ func (voteSet *VoteSet) StringShort() string { return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v +2/3:%v %v}`, voteSet.height, voteSet.round, voteSet.type_, voteSet.maj23Exists, voteSet.votesBitArray) } + +//-------------------------------------------------------------------------------- +// Validation + +func (voteSet *VoteSet) MakeValidation() *types.Validation { + if voteSet.type_ != types.VoteTypePrecommit { + panic("Cannot MakeValidation() unless VoteSet.Type is types.VoteTypePrecommit") + } + voteSet.mtx.Lock() + defer voteSet.mtx.Unlock() + if len(voteSet.maj23Hash) == 0 { + panic("Cannot MakeValidation() unless a blockhash has +2/3") + } + precommits := make([]types.Precommit, voteSet.valSet.Size()) + voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { + vote := voteSet.votes[valIndex] + if vote == nil { + return false + } + if !bytes.Equal(vote.BlockHash, voteSet.maj23Hash) { + return false + } + if !vote.BlockParts.Equals(voteSet.maj23Parts) { + return false + } + precommits[valIndex] = types.Precommit{val.Address, vote.Signature} + return false + }) + return &types.Validation{ + Round: voteSet.round, + Precommits: precommits, + } +} + +// XXX +func VoteSetFromValidation(validation *types.Validation) *VoteSet { + lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) + seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) + for idx, precommit := range seenValidation.Precommits { + precommitVote := &types.Vote{ + Height: state.LastBlockHeight, + Round: seenValidation.Round, + Type: types.VoteTypePrecommit, + BlockHash: state.LastBlockHash, + BlockParts: state.LastBlockParts, + Signature: precommit.Signature, + } + added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) + if !added || err != nil { + panic(Fmt("Failed to reconstruct LastPrecommits: %v", err)) + } + } +} diff --git a/types/block.go b/types/block.go index c42dbeb9..b4f4747d 100644 --- a/types/block.go +++ b/types/block.go @@ -15,9 +15,9 @@ import ( ) type Block struct { - *Header `json:"header"` - *Validation `json:"validation"` - *Data `json:"data"` + *Header `json:"header"` + *Data `json:"data"` + LastValidation *Validation `json:"last_validation"` } // Basic validation that doesn't involve state data. @@ -46,7 +46,7 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight uint, lastBlockHas } */ if b.Header.Height != 1 { - if err := b.Validation.ValidateBasic(); err != nil { + if err := b.LastValidation.ValidateBasic(); err != nil { return err } } @@ -58,12 +58,12 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight uint, lastBlockHas // If the block is incomplete (e.g. missing Header.StateHash) // then the hash is nil, to prevent the usage of that hash. func (b *Block) Hash() []byte { - if b.Header == nil || b.Validation == nil || b.Data == nil { + if b.Header == nil || b.Data == nil || b.LastValidation == nil { return nil } hashHeader := b.Header.Hash() - hashValidation := b.Validation.Hash() hashData := b.Data.Hash() + hashLastValidation := b.LastValidation.Hash() // If hashHeader is nil, required fields are missing. if len(hashHeader) == 0 { @@ -71,7 +71,7 @@ func (b *Block) Hash() []byte { } // Merkle hash from subhashes. - hashes := [][]byte{hashHeader, hashValidation, hashData} + hashes := [][]byte{hashHeader, hashData, hashLastValidation} return merkle.SimpleHashFromHashes(hashes) } @@ -106,8 +106,8 @@ func (b *Block) StringIndented(indent string) string { %s %v %s}#%X`, indent, b.Header.StringIndented(indent+" "), - indent, b.Validation.StringIndented(indent+" "), indent, b.Data.StringIndented(indent+" "), + indent, b.LastValidation.StringIndented(indent+" "), indent, b.Hash()) } @@ -174,56 +174,39 @@ func (h *Header) StringIndented(indent string) string { indent, h.Hash()) } -//----------------------------------------------------------------------------- - -type Precommit struct { - Address []byte `json:"address"` - Signature account.SignatureEd25519 `json:"signature"` -} - -func (pc Precommit) IsZero() bool { - return pc.Signature.IsZero() -} - -func (pc Precommit) String() string { - return fmt.Sprintf("Precommit{A:%X %X}", pc.Address, Fingerprint(pc.Signature)) -} - //------------------------------------- -// NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. -// Any peer with a block can gossip precommits by index with a peer without recalculating the -// active ValidatorSet. +// NOTE: Validation is empty for height 1, but never nil. type Validation struct { - Round uint `json:"round"` // Round for all precommits - Precommits []Precommit `json:"precommits"` // Precommits (or nil) of all active validators in address order. + // NOTE: The Precommits are in order of address to preserve the bonded ValidatorSet order. + // Any peer with a block can gossip precommits by index with a peer without recalculating the + // active ValidatorSet. + Precommits []*Vote `json:"precommits"` // Volatile hash []byte bitArray *BitArray } +func (v *Validation) Height() uint { + if len(v.Precommits) == 0 { + return 0 + } + return v.Precommits[0].Height +} + +func (v *Validation) Round() uint { + if len(v.Precommits) == 0 { + return 0 + } + return v.Precommits[0].Round +} + func (v *Validation) ValidateBasic() error { if len(v.Precommits) == 0 { return errors.New("No precommits in validation") } - lastAddress := []byte{} - for i := 0; i < len(v.Precommits); i++ { - precommit := v.Precommits[i] - if precommit.IsZero() { - if len(precommit.Address) > 0 { - return errors.New("Zero precommits should not have an address") - } - } else { - if len(precommit.Address) == 0 { - return errors.New("Nonzero precommits should have an address") - } - if len(lastAddress) > 0 && bytes.Compare(lastAddress, precommit.Address) != -1 { - return errors.New("Invalid precommit order") - } - lastAddress = precommit.Address - } - } + // TODO Additional validation? return nil } @@ -248,10 +231,8 @@ func (v *Validation) StringIndented(indent string) string { precommitStrings[i] = precommit.String() } return fmt.Sprintf(`Validation{ -%s Round: %v %s Precommits: %v %s}#%X`, - indent, v.Round, indent, strings.Join(precommitStrings, "\n"+indent+" "), indent, v.hash) } From 829df935771d91905aef42f307b46966ef38949f Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Sun, 21 Jun 2015 19:11:21 -0700 Subject: [PATCH 07/24] Re-use ValidatorSet.VerifyValidation() --- blockchain/reactor.go | 4 ++-- state/execution.go | 42 ++++++++-------------------------- state/validator_set.go | 52 +++++++++++++++++++++++------------------- types/block.go | 21 +++++++++++++++-- 4 files changed, 59 insertions(+), 60 deletions(-) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 322ed5c3..01d0865d 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -232,7 +232,7 @@ FOR_LOOP: firstPartsHeader := firstParts.Header() // Finally, verify the first block using the second's validation. err := bcR.state.BondedValidators.VerifyValidation( - bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.Validation) + bcR.state.ChainID, first.Hash(), firstPartsHeader, first.Height, second.LastValidation) if err != nil { log.Debug("error in validation", "error", err) bcR.pool.RedoRequest(first.Height) @@ -244,7 +244,7 @@ FOR_LOOP: // TODO This is bad, are we zombie? panic(Fmt("Failed to process committed block: %v", err)) } - bcR.store.SaveBlock(first, firstParts, second.Validation) + bcR.store.SaveBlock(first, firstParts, second.LastValidation) bcR.state.Save() } } diff --git a/state/execution.go b/state/execution.go index 1d85c949..c68bf4ea 100644 --- a/state/execution.go +++ b/state/execution.go @@ -38,50 +38,26 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade return err } - // Validate block Validation. + // Validate block LastValidation. if block.Height == 1 { - if len(block.Validation.Precommits) != 0 { - return errors.New("Block at height 1 (first block) should have no Validation precommits") + if len(block.LastValidation.Precommits) != 0 { + return errors.New("Block at height 1 (first block) should have no LastValidation precommits") } } else { - if uint(len(block.Validation.Precommits)) != s.LastBondedValidators.Size() { + if uint(len(block.LastValidation.Precommits)) != s.LastBondedValidators.Size() { return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", - s.LastBondedValidators.Size(), len(block.Validation.Precommits))) + s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) } - var sumVotingPower uint64 - s.LastBondedValidators.Iterate(func(index uint, val *Validator) bool { - precommit := block.Validation.Precommits[index] - if precommit.IsZero() { - return false - } else { - vote := &types.Vote{ - Height: block.Height - 1, - Round: block.Validation.Round, - Type: types.VoteTypePrecommit, - BlockHash: block.LastBlockHash, - BlockParts: block.LastBlockParts, - } - if val.PubKey.VerifyBytes(account.SignBytes(s.ChainID, vote), precommit.Signature) { - sumVotingPower += val.VotingPower - return false - } else { - log.Warn(Fmt("Invalid validation signature.\nval: %v\nvote: %v", val, vote)) - err = errors.New("Invalid validation signature") - return true - } - } - }) + err := s.LastBondedValidators.VerifyValidation( + s.ChainID, s.LastBlockHash, s.LastBlockParts, block.Height-1, block.LastValidation) if err != nil { return err } - if sumVotingPower <= s.LastBondedValidators.TotalVotingPower()*2/3 { - return errors.New("Insufficient validation voting power") - } } // Update Validator.LastCommitHeight as necessary. - for i, precommit := range block.Validation.Precommits { - if precommit.IsZero() { + for i, precommit := range block.LastValidation.Precommits { + if precommit == nil { continue } _, val := s.LastBondedValidators.GetByIndex(uint(i)) diff --git a/state/validator_set.go b/state/validator_set.go index da5585c0..af626daf 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -2,7 +2,6 @@ package state import ( "bytes" - "errors" "fmt" "sort" "strings" @@ -201,45 +200,52 @@ func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { } // Verify that +2/3 of the set had signed the given signBytes -func (valSet *ValidatorSet) VerifyValidation(chainID string, hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error { +func (valSet *ValidatorSet) VerifyValidation(chainID string, + hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error { if valSet.Size() != uint(len(v.Precommits)) { - return errors.New(Fmt("Invalid validation -- wrong set size: %v vs %v", - valSet.Size(), len(v.Precommits))) + return fmt.Errorf("Invalid validation -- wrong set size: %v vs %v", valSet.Size(), len(v.Precommits)) + } + if height != v.Height() { + return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, v.Height()) } talliedVotingPower := uint64(0) - seenValidators := map[string]struct{}{} + round := v.Round() for idx, precommit := range v.Precommits { - // may be zero, in which case skip. - if precommit.Signature.IsZero() { + // may be nil if validator skipped. + if precommit == nil { continue } + if precommit.Height != height { + return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, precommit.Height) + } + if precommit.Round != round { + return fmt.Errorf("Invalid validation -- wrong round: %v vs %v", round, precommit.Round) + } + if precommit.Type != types.VoteTypePrecommit { + return fmt.Errorf("Invalid validation -- not precommit @ index %v", idx) + } _, val := valSet.GetByIndex(uint(idx)) - precommitSignBytes := account.SignBytes(chainID, &types.Vote{ - Height: height, Round: v.Round, Type: types.VoteTypePrecommit, - BlockHash: hash, - BlockParts: parts, - }) - - // Validate - if _, seen := seenValidators[string(val.Address)]; seen { - return fmt.Errorf("Duplicate validator for precommit %v for Validation %v", precommit, v) - } - + // Validate signature + precommitSignBytes := account.SignBytes(chainID, precommit) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { - return fmt.Errorf("Invalid signature for precommit %v for Validation %v", precommit, v) + return fmt.Errorf("Invalid validation -- invalid signature: %v", precommit) } - - // Tally - seenValidators[string(val.Address)] = struct{}{} + if !bytes.Equal(precommit.BlockHash, hash) { + continue // Not an error, but doesn't count + } + if !parts.Equals(precommit.BlockParts) { + continue // Not an error, but doesn't count + } + // Good precommit! talliedVotingPower += val.VotingPower } if talliedVotingPower > valSet.TotalVotingPower()*2/3 { return nil } else { - return fmt.Errorf("insufficient voting power %v, needed %v", + return fmt.Errorf("Invalid validation -- insufficient voting power: got %v, needed %v", talliedVotingPower, (valSet.TotalVotingPower()*2/3 + 1)) } } diff --git a/types/block.go b/types/block.go index b4f4747d..59e26a80 100644 --- a/types/block.go +++ b/types/block.go @@ -206,7 +206,24 @@ func (v *Validation) ValidateBasic() error { if len(v.Precommits) == 0 { return errors.New("No precommits in validation") } - // TODO Additional validation? + height, round := v.Height(), v.Round() + for _, precommit := range v.Precommits { + // Ensure that all votes are precommits + if precommit.Type != VoteTypePrecommit { + return fmt.Errorf("Invalid validation vote. Expected precommit, got %v", + precommit.Type) + } + // Ensure that all heights are the same + if precommit.Height != height { + return fmt.Errorf("Invalid validation precommit height. Expected %v, got %v", + height, precommit.Height) + } + // Ensure that all rounds are the same + if precommit.Round != round { + return fmt.Errorf("Invalid validation precommit round. Expected %v, got %v", + round, precommit.Round) + } + } return nil } @@ -241,7 +258,7 @@ func (v *Validation) BitArray() *BitArray { if v.bitArray == nil { v.bitArray = NewBitArray(uint(len(v.Precommits))) for i, precommit := range v.Precommits { - v.bitArray.SetIndex(uint(i), !precommit.IsZero()) + v.bitArray.SetIndex(uint(i), precommit != nil) } } return v.bitArray From 3db63477aaceb15b3de7f641fe49561247279449 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 22 Jun 2015 13:46:25 -0700 Subject: [PATCH 08/24] An empty BitArray is nil. --- common/bit_array.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/bit_array.go b/common/bit_array.go index eb314335..034beb0f 100644 --- a/common/bit_array.go +++ b/common/bit_array.go @@ -13,7 +13,11 @@ type BitArray struct { Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported } +// There is no BitArray whose Size is 0. Use nil instead. func NewBitArray(bits uint) *BitArray { + if bits == 0 { + return nil + } return &BitArray{ Bits: bits, Elems: make([]uint64, (bits+63)/64), @@ -168,10 +172,6 @@ func (bA *BitArray) IsFull() bool { bA.mtx.Lock() defer bA.mtx.Unlock() - if bA.Bits == 0 { - return false - } - // Check all elements except the last for _, elem := range bA.Elems[:len(bA.Elems)-1] { if (^elem) != 0 { From 9b96e2e17132a4086296c12b5ed6c49c6816afa8 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Mon, 22 Jun 2015 19:04:31 -0700 Subject: [PATCH 09/24] ProposalPOLRound... --- blockchain/store.go | 2 +- consensus/height_vote_set.go | 22 +-- consensus/reactor.go | 293 ++++++++++++++++++++--------------- consensus/state.go | 7 + consensus/types/proposal.go | 26 ++-- consensus/vote_set.go | 4 +- types/block_meta.go | 12 +- 7 files changed, 212 insertions(+), 154 deletions(-) diff --git a/blockchain/store.go b/blockchain/store.go index 67f6ee96..d0618a23 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -62,7 +62,7 @@ func (bs *BlockStore) LoadBlock(height uint) *types.Block { panic(Fmt("Error reading block meta: %v", err)) } bytez := []byte{} - for i := uint(0); i < meta.Parts.Total; i++ { + for i := uint(0); i < meta.PartsHeader.Total; i++ { part := bs.LoadBlockPart(height, i) bytez = append(bytez, part.Bytes...) } diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index b345c08b..705d648e 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -37,10 +37,10 @@ type HeightVoteSet struct { func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { hvs := &HeightVoteSet{ - height: height, - valSet: valSet, - roundVoteSets: make(map[uint]RoundVoteSet), - peerFFPrevotes: make(map[string]*VoteSet), + height: height, + valSet: valSet, + roundVoteSets: make(map[uint]RoundVoteSet), + peerFastForward: make(map[string]uint), } hvs.SetRound(0) return hvs @@ -64,7 +64,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) { panic("SetRound() must increment hvs.round") } for r := hvs.round + 1; r <= round; r++ { - if _, ok := hvs.roundVoteSet[r]; ok { + if _, ok := hvs.roundVoteSets[r]; ok { continue // Already exists because peerFastForward. } hvs.addRound(round) @@ -73,18 +73,18 @@ func (hvs *HeightVoteSet) SetRound(round uint) { } func (hvs *HeightVoteSet) addRound(round uint) { - if _, ok := hvs.roundVoteSet[r]; ok { + if _, ok := hvs.roundVoteSets[round]; ok { panic("addRound() for an existing round") } - prevotes := NewVoteSet(hvs.height, r, types.VoteTypePrevote, hvs.valSet) - precommits := NewVoteSet(hvs.height, r, types.VoteTypePrecommit, hvs.valSet) - hvs.roundVoteSets[r] = RoundVoteSet{ + prevotes := NewVoteSet(hvs.height, round, types.VoteTypePrevote, hvs.valSet) + precommits := NewVoteSet(hvs.height, round, types.VoteTypePrecommit, hvs.valSet) + hvs.roundVoteSets[round] = RoundVoteSet{ Prevotes: prevotes, Precommits: precommits, } } -// CONTRACT: if err == nil, added == true +// Duplicate votes return added=false, err=nil. func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peer string) (added bool, index uint, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() @@ -92,7 +92,7 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peer st if voteSet == nil { if _, ok := hvs.peerFastForward[peer]; !ok { hvs.addRound(vote.Round) - hvs.peerFastForwards[peer] = vote.Round + hvs.peerFastForward[peer] = vote.Round } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! diff --git a/consensus/reactor.go b/consensus/reactor.go index d0ba250b..7ef9ec02 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -164,13 +164,11 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte case *ProposalMessage: ps.SetHasProposal(msg.Proposal) err = conR.conS.SetProposal(msg.Proposal) - case *PartMessage: - if msg.Type == partTypeProposalBlock { - ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Proof.Index) - _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Round, msg.Part) - } else { - log.Warn(Fmt("Unknown part type %v", msg.Type)) - } + case *ProposalPOLMessage: + ps.ApplyProposalPOLMessage(msg) + case *BlockPartMessage: + ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Proof.Index) + _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Round, msg.Part) default: log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) } @@ -203,21 +201,17 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } else { // Probably an invalid signature. Bad peer. log.Warn("Error attempting to add vote", "error", err) - // TODO: punish peer } } - // Initialize Prevotes/Precommits if needed - ps.EnsureVoteBitArrays(rs.Height, rs.Validators.Size(), _) + ps.EnsureVoteBitArrays(rs.Height, rs.Validators.Size(), nil) + ps.EnsureVoteBitArrays(rs.Height-1, rs.LastCommit.Size(), nil) ps.SetHasVote(vote, index) if added { - msg := &HasVoteMessage{ - Height: vote.Height, - Round: vote.Round, - Type: vote.Type, - Index: index, - } - conR.sw.Broadcast(StateChannel, msg) + // If rs.Height == vote.Height && rs.Round < vote.Round, + // the peer is sending us CatchupCommit precommits. + // We could make note of this and help filter in broadcastHasVoteMessage(). + conR.broadcastHasVoteMessage(vote, index) } default: @@ -232,6 +226,32 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } } +// Broadcasts HasVoteMessage to peers that care. +func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote, index uint) { + msg := &HasVoteMessage{ + Height: vote.Height, + Round: vote.Round, + Type: vote.Type, + Index: index, + } + conR.sw.Broadcast(StateChannel, msg) + /* + // TODO: Make this broadcast more selective. + for _, peer := range conR.sw.Peers().List() { + ps := peer.Data.Get(PeerStateKey).(*PeerState) + prs := ps.GetRoundState() + if prs.Height == vote.Height { + // TODO: Also filter on round? + peer.TrySend(StateChannel, msg) + } else { + // Height doesn't match + // TODO: check a field, maybe CatchupCommitRound? + // TODO: But that requires changing the struct field comment. + } + } + */ +} + // Sets our private validator account for signing votes. func (conR *ConsensusReactor) SetPrivValidator(priv *sm.PrivValidator) { conR.conS.SetPrivValidator(priv) @@ -254,27 +274,20 @@ func (conR *ConsensusReactor) SetFireable(evsw events.Fireable) { //-------------------------------------- func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg *CommitStepMessage) { - // Get seconds since beginning of height. - timeElapsed := time.Now().Sub(rs.StartTime) - - // Broadcast NewRoundStepMessage nrsMsg = &NewRoundStepMessage{ Height: rs.Height, Round: rs.Round, Step: rs.Step, - SecondsSinceStartTime: uint(timeElapsed.Seconds()), + SecondsSinceStartTime: uint(time.Now().Sub(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } - - // If the step is commit, then also broadcast a CommitStepMessage. if rs.Step == RoundStepCommit { csMsg = &CommitStepMessage{ - Height: rs.Height, - BlockParts: rs.ProposalBlockParts.Header(), - BlockBitArray: rs.ProposalBlockParts.BitArray(), + Height: rs.Height, + BlockPartsHeader: rs.ProposalBlockParts.Header(), + BlockParts: rs.ProposalBlockParts.BitArray(), } } - return } @@ -324,14 +337,13 @@ OUTER_LOOP: prs := ps.GetRoundState() // Send proposal Block parts? - if rs.ProposalBlockParts.HasHeader(prs.ProposalBlockParts) { + if rs.ProposalBlockParts.HasHeader(prs.ProposalBlockPartsHeader) { //log.Debug("ProposalBlockParts matched", "blockParts", prs.ProposalBlockParts) - if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockBitArray.Copy()).PickRandom(); ok { + if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockParts.Copy()).PickRandom(); ok { part := rs.ProposalBlockParts.GetPart(index) - msg := &PartMessage{ + msg := &BlockPartMessage{ Height: rs.Height, Round: rs.Round, - Type: partTypeProposalBlock, Part: part, } peer.Send(DataChannel, msg) @@ -342,13 +354,13 @@ OUTER_LOOP: // If the peer is on a previous height, help catch up. if 0 < prs.Height && prs.Height < rs.Height { - //log.Debug("Data catchup", "height", rs.Height, "peerHeight", prs.Height, "peerProposalBlockBitArray", prs.ProposalBlockBitArray) - if index, ok := prs.ProposalBlockBitArray.Not().PickRandom(); ok { + //log.Debug("Data catchup", "height", rs.Height, "peerHeight", prs.Height, "peerProposalBlockParts", prs.ProposalBlockParts) + if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { // Ensure that the peer's PartSetHeader is correct blockMeta := conR.blockStore.LoadBlockMeta(prs.Height) - if !blockMeta.Parts.Equals(prs.ProposalBlockParts) { - log.Debug("Peer ProposalBlockParts mismatch, sleeping", - "peerHeight", prs.Height, "blockParts", blockMeta.Parts, "peerBlockParts", prs.ProposalBlockParts) + if !blockMeta.PartsHeader.Equals(prs.ProposalBlockPartsHeader) { + log.Debug("Peer ProposalBlockPartsHeader mismatch, sleeping", + "peerHeight", prs.Height, "blockPartsHeader", blockMeta.PartsHeader, "peerBlockPartsHeader", prs.ProposalBlockPartsHeader) time.Sleep(peerGossipSleepDuration) continue OUTER_LOOP } @@ -356,15 +368,14 @@ OUTER_LOOP: part := conR.blockStore.LoadBlockPart(prs.Height, index) if part == nil { log.Warn("Could not load part", "index", index, - "peerHeight", prs.Height, "blockParts", blockMeta.Parts, "peerBlockParts", prs.ProposalBlockParts) + "peerHeight", prs.Height, "blockPartsHeader", blockMeta.PartsHeader, "peerBlockPartsHeader", prs.ProposalBlockPartsHeader) time.Sleep(peerGossipSleepDuration) continue OUTER_LOOP } // Send the part - msg := &PartMessage{ + msg := &BlockPartMessage{ Height: prs.Height, Round: prs.Round, - Type: partTypeProposalBlock, Part: part, } peer.Send(DataChannel, msg) @@ -384,11 +395,27 @@ OUTER_LOOP: continue OUTER_LOOP } - // Send proposal? + // By here, height and round match. + + // Send Proposal && ProposalPOL BitArray? if rs.Proposal != nil && !prs.Proposal { - msg := &ProposalMessage{Proposal: rs.Proposal} - peer.Send(DataChannel, msg) - ps.SetHasProposal(rs.Proposal) + // Proposal + { + msg := &ProposalMessage{Proposal: rs.Proposal} + peer.Send(DataChannel, msg) + ps.SetHasProposal(rs.Proposal) + } + // ProposalPOL. + // Must be in the same channel, sequential. + // That is, peer must receive ProposalMessage first. + if 0 <= rs.Proposal.POLRound { + msg := &ProposalPOLMessage{ + Height: rs.Height, + ProposalPOLRound: uint(rs.Proposal.POLRound), + ProposalPOL: rs.Votes.Prevotes(uint(rs.Proposal.POLRound)).BitArray(), + } + peer.Send(DataChannel, msg) + } continue OUTER_LOOP } @@ -473,36 +500,42 @@ OUTER_LOOP: if rs.Height == prs.Height { // If there are lastCommits to send... if prs.Step == RoundStepNewHeight { - if trySendVote(rs.LastCommit, prs.LastCommit) { + if trySendVote(rs.LastCommit, &prs.LastCommit) { continue OUTER_LOOP } } // If there are prevotes to send... if rs.Round == prs.Round && prs.Step <= RoundStepPrevote { - if trySendVote(rs.Prevotes, prs.Prevotes) { + if trySendVote(rs.Votes.Prevotes(rs.Round), &prs.Prevotes) { continue OUTER_LOOP } } // If there are precommits to send... if rs.Round == prs.Round && prs.Step <= RoundStepPrecommit { - if trySendVote(rs.Precommits, prs.Precommits) { + if trySendVote(rs.Votes.Precommits(rs.Round), &prs.Precommits) { continue OUTER_LOOP } } + // If there are POLPrevotes to send... + if 0 <= prs.ProposalPOLRound { + if polPrevotes := rs.Votes.Prevotes(uint(prs.ProposalPOLRound)); polPrevotes != nil { + if trySendVote(polPrevotes, &prs.ProposalPOL) { + continue OUTER_LOOP + } + } + } } // Special catchup logic. // If peer is lagging by height 1, send LastCommit. if prs.Height != 0 && prs.Height == rs.Height-1 { if prs.Round == rs.LastCommit.Round() { - if trySendVote(rs.LastCommit, prs.Precommits) { + if trySendVote(rs.LastCommit, &prs.Precommits) { continue OUTER_LOOP - // XXX CONTONUE } } else { ps.SetCatchupCommitRound(prs.Height, rs.LastCommit.Round()) - ps.EnsureVoteBitArrays(prs.Height, rs.LastCommit.Size(), prs) - if trySendVote(rs.LastCommit, prs.CatchupCommit) { + if trySendVote(rs.LastCommit, &prs.CatchupCommit) { continue OUTER_LOOP } } @@ -515,15 +548,15 @@ OUTER_LOOP: // which contains precommit signatures for prs.Height. validation := conR.blockStore.LoadBlockValidation(prs.Height) log.Debug("Loaded BlockValidation for catch-up", "height", prs.Height, "validation", validation) - // Peer's CommitRound should be -1 or equal to the validation's precommit rounds. + // Peer's CatchupCommitRound should be -1 or equal to the validation's precommit rounds. // If not, warn. - if prs.CommitRound == -1 { - ps.SetCommitRound(prs.Height, validation.Round()) + if prs.CatchupCommitRound == -1 { + ps.SetCatchupCommitRound(prs.Height, validation.Round()) continue OUTER_LOOP // Get prs := ps.GetRoundState() again. - } else if prs.CommitRound != validation.Round() { - log.Warn("Peer's CommitRound during catchup not equal to commit round", - "height", prs.Height, "validation", validation, "prs.CommitRound", prs.CommitRound) - } else if trySendPrecommitFromValidation(validation, prs.Commit) { + } else if prs.CatchupCommitRound != int(validation.Round()) { + log.Warn("Peer's CatchupCommitRound during catchup not equal to commit round", + "height", prs.Height, "validation", validation, "prs.CatchupCommitRound", prs.CatchupCommitRound) + } else if trySendPrecommitFromValidation(validation, &prs.CatchupCommit) { continue OUTER_LOOP } } @@ -532,8 +565,8 @@ OUTER_LOOP: // We sent nothing. Sleep... sleeping = 1 log.Debug("No votes to send, sleeping", "peer", peer, - "localPV", rs.Prevotes.BitArray(), "peerPV", prs.Prevotes, - "localPC", rs.Precommits.BitArray(), "peerPC", prs.Precommits) + "localPV", rs.Votes.Prevotes(rs.Round).BitArray(), "peerPV", prs.Prevotes, + "localPC", rs.Votes.Precommits(rs.Round).BitArray(), "peerPC", prs.Precommits) } else if sleeping == 2 { // Continued sleep... sleeping = 1 @@ -548,24 +581,21 @@ OUTER_LOOP: // Read only when returned by PeerState.GetRoundState(). type PeerRoundState struct { - Height uint // Height peer is at - Round uint // Round peer is at - Step RoundStepType // Step peer is at - StartTime time.Time // Estimated start of round 0 at this height - Proposal bool // True if peer has proposal for this round - ProposalBlockParts types.PartSetHeader // - ProposalBlockBitArray *BitArray // True bit -> has part - ProposalPOLParts types.PartSetHeader // - ProposalPOLBitArray *BitArray // True bit -> has part - Prevotes *BitArray // All votes peer has for this round - Precommits *BitArray // All precommits peer has for this round - LastCommitRound uint // Round of commit for last height. - LastCommit *BitArray // All commit precommits of commit for last height. - - // If peer is leading in height, the round that peer believes commit round is. - // If peer is lagging in height, the round that we believe commit round is. - CatchupCommitRound int - CatchupCommit *BitArray // All commit precommits peer has for this height + Height uint // Height peer is at + Round uint // Round peer is at + Step RoundStepType // Step peer is at + StartTime time.Time // Estimated start of round 0 at this height + Proposal bool // True if peer has proposal for this round + ProposalBlockPartsHeader types.PartSetHeader // + ProposalBlockParts *BitArray // + ProposalPOLRound int // -1 if none + ProposalPOL *BitArray // nil until ProposalPOLMessage received. + Prevotes *BitArray // All votes peer has for this round + Precommits *BitArray // All precommits peer has for this round + LastCommitRound uint // Round of commit for last height. + LastCommit *BitArray // All commit precommits of commit for last height. + CatchupCommitRound int // Round that we believe commit round is. + CatchupCommit *BitArray // All commit precommits peer has for this height } //----------------------------------------------------------------------------- @@ -608,10 +638,10 @@ func (ps *PeerState) SetHasProposal(proposal *Proposal) { } ps.Proposal = true - ps.ProposalBlockParts = proposal.BlockParts - ps.ProposalBlockBitArray = NewBitArray(uint(proposal.BlockParts.Total)) - ps.ProposalPOLParts = proposal.POLParts - ps.ProposalPOLBitArray = NewBitArray(uint(proposal.POLParts.Total)) + ps.ProposalBlockPartsHeader = proposal.BlockPartsHeader + ps.ProposalBlockParts = NewBitArray(uint(proposal.BlockPartsHeader.Total)) + ps.ProposalPOLRound = proposal.POLRound + ps.ProposalPOL = nil // Nil until ProposalPOLMessage received. } func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint) { @@ -622,18 +652,7 @@ func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint return } - ps.ProposalBlockBitArray.SetIndex(uint(index), true) -} - -func (ps *PeerState) SetHasProposalPOLPart(height uint, round uint, index uint) { - ps.mtx.Lock() - defer ps.mtx.Unlock() - - if ps.Height != height || ps.Round != round { - return - } - - ps.ProposalPOLBitArray.SetIndex(uint(index), true) + ps.ProposalBlockParts.SetIndex(uint(index), true) } // prs: If given, will also update this PeerRoundState copy. @@ -651,6 +670,9 @@ func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *P if ps.CatchupCommit == nil { ps.CatchupCommit = NewBitArray(numValidators) } + if ps.ProposalPOL == nil { + ps.ProposalPOL = NewBitArray(numValidators) + } } else if ps.Height == height+1 { if ps.LastCommit == nil { ps.LastCommit = NewBitArray(numValidators) @@ -663,6 +685,7 @@ func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *P prs.Precommits = ps.Precommits prs.LastCommit = ps.LastCommit prs.CatchupCommit = ps.CatchupCommit + prs.ProposalPOL = ps.ProposalPOL } } @@ -684,13 +707,17 @@ func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) return } + // By here, ps.Height is height. switch type_ { case types.VoteTypePrevote: + if ps.ProposalPOLRound == int(round) { + ps.ProposalPOL.SetIndex(index, true) + } ps.Prevotes.SetIndex(index, true) log.Debug("SetHasVote", "peer", ps.Key, "prevotes", ps.Prevotes, "index", index) case types.VoteTypePrecommit: - if ps.CommitRound == round { - ps.Commit.SetIndex(index, true) + if ps.CatchupCommitRound == int(round) { + ps.CatchupCommit.SetIndex(index, true) } ps.Precommits.SetIndex(index, true) log.Debug("SetHasVote", "peer", ps.Key, "precommits", ps.Precommits, "index", index) @@ -706,7 +733,7 @@ func (ps *PeerState) SetCatchupCommitRound(height, round uint) { if ps.Height != height { return } - if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { + if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != int(round) { log.Warn("Conflicting CatchupCommitRound", "height", height, "orig", ps.CatchupCommitRound, @@ -714,7 +741,7 @@ func (ps *PeerState) SetCatchupCommitRound(height, round uint) { ) // TODO think harder } - ps.CatchupCommitRound = round + ps.CatchupCommitRound = int(round) ps.CatchupCommit = nil } @@ -727,7 +754,7 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun psRound := ps.Round //psStep := ps.Step psCatchupCommitRound := ps.CatchupCommitRound - psCatchupCommit := ps.CatchupCommitRound + psCatchupCommit := ps.CatchupCommit startTime := time.Now().Add(-1 * time.Duration(msg.SecondsSinceStartTime) * time.Second) ps.Height = msg.Height @@ -736,15 +763,15 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun ps.StartTime = startTime if psHeight != msg.Height || psRound != msg.Round { ps.Proposal = false - ps.ProposalBlockParts = types.PartSetHeader{} - ps.ProposalBlockBitArray = nil - ps.ProposalPOLParts = types.PartSetHeader{} - ps.ProposalPOLBitArray = nil + ps.ProposalBlockPartsHeader = types.PartSetHeader{} + ps.ProposalBlockParts = nil + ps.ProposalPOLRound = -1 + ps.ProposalPOL = nil // We'll update the BitArray capacity later. ps.Prevotes = nil ps.Precommits = nil } - if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound { + if psHeight == msg.Height && psRound != msg.Round && int(msg.Round) == psCatchupCommitRound { // Peer caught up to CatchupCommitRound. ps.Precommits = psCatchupCommit } @@ -771,8 +798,8 @@ func (ps *PeerState) ApplyCommitStepMessage(msg *CommitStepMessage) { return } + ps.ProposalBlockPartsHeader = msg.BlockPartsHeader ps.ProposalBlockParts = msg.BlockParts - ps.ProposalBlockBitArray = msg.BlockBitArray } func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) { @@ -786,6 +813,22 @@ func (ps *PeerState) ApplyHasVoteMessage(msg *HasVoteMessage) { ps.setHasVote(msg.Height, msg.Round, msg.Type, msg.Index) } +func (ps *PeerState) ApplyProposalPOLMessage(msg *ProposalPOLMessage) { + ps.mtx.Lock() + defer ps.mtx.Unlock() + + if ps.Height != msg.Height { + return + } + if ps.ProposalPOLRound != int(msg.ProposalPOLRound) { + return + } + + // TODO: Merge onto existing ps.ProposalPOL? + // We might have sent some prevotes in the meantime. + ps.ProposalPOL = msg.ProposalPOL +} + //----------------------------------------------------------------------------- // Messages @@ -793,9 +836,10 @@ const ( msgTypeNewRoundStep = byte(0x01) msgTypeCommitStep = byte(0x02) msgTypeProposal = byte(0x11) - msgTypePart = byte(0x12) // both block & POL - msgTypeVote = byte(0x13) - msgTypeHasVote = byte(0x14) + msgTypeProposalPOL = byte(0x12) + msgTypeBlockPart = byte(0x13) // both block & POL + msgTypeVote = byte(0x14) + msgTypeHasVote = byte(0x15) ) type ConsensusMessage interface{} @@ -805,7 +849,8 @@ var _ = binary.RegisterInterface( binary.ConcreteType{&NewRoundStepMessage{}, msgTypeNewRoundStep}, binary.ConcreteType{&CommitStepMessage{}, msgTypeCommitStep}, binary.ConcreteType{&ProposalMessage{}, msgTypeProposal}, - binary.ConcreteType{&PartMessage{}, msgTypePart}, + binary.ConcreteType{&ProposalPOLMessage{}, msgTypeProposalPOL}, + binary.ConcreteType{&BlockPartMessage{}, msgTypeBlockPart}, binary.ConcreteType{&VoteMessage{}, msgTypeVote}, binary.ConcreteType{&HasVoteMessage{}, msgTypeHasVote}, ) @@ -838,13 +883,13 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- type CommitStepMessage struct { - Height uint - BlockParts types.PartSetHeader - BlockBitArray *BitArray + Height uint + BlockPartsHeader types.PartSetHeader + BlockParts *BitArray } func (m *CommitStepMessage) String() string { - return fmt.Sprintf("[CommitStep H:%v BP:%v BA:%v]", m.Height, m.BlockParts, m.BlockBitArray) + return fmt.Sprintf("[CommitStep H:%v BP:%v BA:%v]", m.Height, m.BlockPartsHeader, m.BlockParts) } //------------------------------------- @@ -859,20 +904,26 @@ func (m *ProposalMessage) String() string { //------------------------------------- -const ( - partTypeProposalBlock = byte(0x01) - partTypeProposalPOL = byte(0x02) -) +type ProposalPOLMessage struct { + Height uint + ProposalPOLRound uint + ProposalPOL *BitArray +} -type PartMessage struct { +func (m *ProposalPOLMessage) String() string { + return fmt.Sprintf("[ProposalPOL H:%v POLR:%v POL:%v]", m.Height, m.ProposalPOLRound, m.ProposalPOL) +} + +//------------------------------------- + +type BlockPartMessage struct { Height uint Round uint - Type byte Part *types.Part } -func (m *PartMessage) String() string { - return fmt.Sprintf("[Part H:%v R:%v T:%X P:%v]", m.Height, m.Round, m.Type, m.Part) +func (m *BlockPartMessage) String() string { + return fmt.Sprintf("[BlockPart H:%v R:%v T:%X P:%v]", m.Height, m.Round, m.Part) } //------------------------------------- diff --git a/consensus/state.go b/consensus/state.go index 4c4858aa..078c3e07 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -126,6 +126,7 @@ var ( var ( ErrInvalidProposalSignature = errors.New("Error invalid proposal signature") + ErrInvalidProposalPOLRound = errors.New("Error invalid proposal POL round") ) //----------------------------------------------------------------------------- @@ -900,6 +901,12 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { return nil } + // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. + if proposal.POLRound != -1 && + (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { + return ErrInvalidProposalPOLRound + } + // Verify signature if !cs.Validators.Proposer().PubKey.VerifyBytes(account.SignBytes(cs.state.ChainID, proposal), proposal.Signature) { return ErrInvalidProposalSignature diff --git a/consensus/types/proposal.go b/consensus/types/proposal.go index 5009e681..a3a9f546 100644 --- a/consensus/types/proposal.go +++ b/consensus/types/proposal.go @@ -17,31 +17,31 @@ var ( ) type Proposal struct { - Height uint `json:"height"` - Round uint `json:"round"` - BlockParts types.PartSetHeader `json:"block_parts"` - POLRound int `json:"pol_round"` // -1 if null. - Signature account.SignatureEd25519 `json:"signature"` + Height uint `json:"height"` + Round uint `json:"round"` + BlockPartsHeader types.PartSetHeader `json:"block_parts_header"` + POLRound int `json:"pol_round"` // -1 if null. + Signature account.SignatureEd25519 `json:"signature"` } -func NewProposal(height uint, round uint, blockParts types.PartSetHeader, polRound int) *Proposal { +func NewProposal(height uint, round uint, blockPartsHeader types.PartSetHeader, polRound int) *Proposal { return &Proposal{ - Height: height, - Round: round, - BlockParts: blockParts, - POLRound: polRound, + Height: height, + Round: round, + BlockPartsHeader: blockPartsHeader, + POLRound: polRound, } } func (p *Proposal) String() string { return fmt.Sprintf("Proposal{%v/%v %v %v %v}", p.Height, p.Round, - p.BlockParts, p.POLRound, p.Signature) + p.BlockPartsHeader, p.POLRound, p.Signature) } func (p *Proposal) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { binary.WriteTo([]byte(Fmt(`{"chain_id":"%s"`, chainID)), w, n, err) - binary.WriteTo([]byte(`,"proposal":{"block_parts":`), w, n, err) - p.BlockParts.WriteSignBytes(w, n, err) + binary.WriteTo([]byte(`,"proposal":{"block_parts_header":`), w, n, err) + p.BlockPartsHeader.WriteSignBytes(w, n, err) binary.WriteTo([]byte(Fmt(`,"height":%v,"pol_round":%v`, p.Height, p.POLRound)), w, n, err) binary.WriteTo([]byte(Fmt(`,"round":%v}}`, p.Round)), w, n, err) } diff --git a/consensus/vote_set.go b/consensus/vote_set.go index bcb8fbfa..f287b354 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -77,7 +77,7 @@ func (voteSet *VoteSet) Size() uint { // Returns added=true, index if vote was added // Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] -// CONTRACT: if err == nil, added == true +// Duplicate votes return added=false, err=nil. // NOTE: vote should not be mutated after adding. func (voteSet *VoteSet) AddByIndex(valIndex uint, vote *types.Vote) (added bool, index uint, err error) { voteSet.mtx.Lock() @@ -88,7 +88,7 @@ func (voteSet *VoteSet) AddByIndex(valIndex uint, vote *types.Vote) (added bool, // Returns added=true, index if vote was added // Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] -// CONTRACT: if err == nil, added == true +// Duplicate votes return added=false, err=nil. // NOTE: vote should not be mutated after adding. func (voteSet *VoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { voteSet.mtx.Lock() diff --git a/types/block_meta.go b/types/block_meta.go index 540da8d1..b72c0c86 100644 --- a/types/block_meta.go +++ b/types/block_meta.go @@ -1,15 +1,15 @@ package types type BlockMeta struct { - Hash []byte `json:"hash"` // The block hash - Header *Header `json:"header"` // The block's Header - Parts PartSetHeader `json:"parts"` // The PartSetHeader, for transfer + Hash []byte `json:"hash"` // The block hash + Header *Header `json:"header"` // The block's Header + PartsHeader PartSetHeader `json:"parts_header"` // The PartSetHeader, for transfer } func NewBlockMeta(block *Block, blockParts *PartSet) *BlockMeta { return &BlockMeta{ - Hash: block.Hash(), - Header: block.Header, - Parts: blockParts.Header(), + Hash: block.Hash(), + Header: block.Header, + PartsHeader: blockParts.Header(), } } From 9e1794eaea69830a46442ecc3fa177a5a42a58cc Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 24 Jun 2015 14:04:40 -0700 Subject: [PATCH 10/24] Fixed tests --- consensus/height_vote_set.go | 14 ++-- consensus/reactor.go | 2 +- consensus/state.go | 107 ++++++++++++++++++------------- consensus/state_test.go | 10 ++- consensus/types/proposal_test.go | 13 ++-- consensus/vote_set.go | 30 ++------- rpc/test/helpers.go | 5 +- state/state_test.go | 15 ++--- types/block.go | 9 ++- 9 files changed, 102 insertions(+), 103 deletions(-) diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index 705d648e..ebd94820 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -42,7 +42,8 @@ func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { roundVoteSets: make(map[uint]RoundVoteSet), peerFastForward: make(map[string]uint), } - hvs.SetRound(0) + hvs.addRound(0) + hvs.round = 0 return hvs } @@ -85,14 +86,15 @@ func (hvs *HeightVoteSet) addRound(round uint) { } // Duplicate votes return added=false, err=nil. -func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peer string) (added bool, index uint, err error) { +// By convention, peerKey is "" if origin is self. +func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { - if _, ok := hvs.peerFastForward[peer]; !ok { + if _, ok := hvs.peerFastForward[peerKey]; !ok { hvs.addRound(vote.Round) - hvs.peerFastForward[peer] = vote.Round + hvs.peerFastForward[peerKey] = vote.Round } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! @@ -122,8 +124,8 @@ func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet { func (hvs *HeightVoteSet) POLRound() int { hvs.mtx.Lock() defer hvs.mtx.Unlock() - for r := hvs.round; r >= 0; r-- { - if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() { + for r := int(hvs.round); r >= 0; r-- { + if hvs.getVoteSet(uint(r), types.VoteTypePrevote).HasTwoThirdsMajority() { return int(r) } } diff --git a/consensus/reactor.go b/consensus/reactor.go index 7ef9ec02..c8cce2d7 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -187,7 +187,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } VOTE_PASS: address, _ := rs.Validators.GetByIndex(msg.ValidatorIndex) - added, index, err := conR.conS.AddVote(address, vote) + added, index, err := conR.conS.AddVote(address, vote, peer.Key) if err != nil { // If conflicting sig, broadcast evidence tx for slashing. Else punish peer. if errDupe, ok := err.(*types.ErrVoteConflictingSignature); ok { diff --git a/consensus/state.go b/consensus/state.go index 078c3e07..4fd5b183 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -252,6 +252,8 @@ func NewConsensusState(state *sm.State, blockStore *bc.BlockStore, mempoolReacto newStepCh: make(chan *RoundState, 10), } cs.updateToState(state, true) + // Don't call scheduleRound0 yet. + // We do that upon Start(). cs.maybeRebond() cs.reconstructLastCommit(state) return cs @@ -268,7 +270,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { for idx, precommit := range seenValidation.Precommits { precommitVote := &types.Vote{ Height: state.LastBlockHeight, - Round: seenValidation.Round, + Round: seenValidation.Round(), Type: types.VoteTypePrecommit, BlockHash: state.LastBlockHash, BlockParts: state.LastBlockParts, @@ -309,11 +311,12 @@ func (cs *ConsensusState) NewStepCh() chan *RoundState { func (cs *ConsensusState) Start() { if atomic.CompareAndSwapUint32(&cs.started, 0, 1) { log.Info("Starting ConsensusState") - cs.scheduleRound0() + cs.scheduleRound0(cs.Height) } } func (cs *ConsensusState) scheduleRound0(height uint) { + log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) sleepDuration := cs.StartTime.Sub(time.Now()) go func() { if sleepDuration > 0 { @@ -423,7 +426,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { func (cs *ConsensusState) EnterNewRound(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round >= round { + if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != RoundStepNewHeight) { log.Debug(Fmt("EnterNewRound(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -432,8 +435,9 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) { } // Increment validators if necessary + validators := cs.Validators if cs.Round < round { - validators := cs.Validators.Copy() + validators = validators.Copy() validators.IncrementAccum(round - cs.Round) } @@ -517,18 +521,23 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { } else { log.Warn("EnterPropose: Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err) } + } func (cs *ConsensusState) isProposalComplete() bool { if cs.Proposal == nil || cs.ProposalBlock == nil { return false } - return cs.Votes.Prevote(cs.Proposal.POLRound).HasTwoThirdsMajority() + if cs.Proposal.POLRound < 0 { + return true + } else { + return cs.Votes.Prevotes(uint(cs.Proposal.POLRound)).HasTwoThirdsMajority() + } } // Create the next block to propose and return it. // NOTE: make it side-effect free for clarity. -func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { +func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts *types.PartSet) { var validation *types.Validation if cs.Height == 1 { // We're creating a proposal for the first block. @@ -554,7 +563,7 @@ func (cs *ConsensusState) createProposalBlock() (*types.Block, *types.PartSet) { LastBlockParts: cs.state.LastBlockParts, StateHash: nil, // Will set afterwards. }, - Validation: validation, + LastValidation: validation, Data: &types.Data{ Txs: txs, }, @@ -645,9 +654,9 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { cs.Step = RoundStepPrevoteWait cs.newStepCh <- cs.getRoundState() - // After `timeoutPrevote`, EnterPrecommit() + // After `timeoutPrevote0+timeoutPrevoteDelta*round`, EnterPrecommit() go func() { - time.Sleep(timeoutPrevote) + time.Sleep(timeoutPrevote0 + timeoutPrevote0*time.Duration(round)) cs.EnterPrecommit(height, round) }() } @@ -734,7 +743,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { cs.ProposalBlock = nil cs.ProposalBlockParts = types.NewPartSetFromHeader(partsHeader) } - cs.signAddVote(types.VoteTypePrecommit, nil, PartSetHeader{}) + cs.signAddVote(types.VoteTypePrecommit, nil, types.PartSetHeader{}) return } @@ -755,9 +764,9 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { cs.Step = RoundStepPrecommitWait cs.newStepCh <- cs.getRoundState() - // After `timeoutPrecommit`, EnterNewRound() + // After `timeoutPrecommit0+timeoutPrecommitDelta*round`, EnterNewRound() go func() { - time.Sleep(timeoutPrecommit) + time.Sleep(timeoutPrecommit0 + timeoutPrecommitDelta*time.Duration(round)) // If we have +2/3 of precommits for a particular block (or nil), // we already entered commit (or the next round). // So just try to transition to the next round, @@ -782,11 +791,11 @@ func (cs *ConsensusState) EnterCommit(height uint) { cs.newStepCh <- cs.getRoundState() // Maybe finalize immediately. - cs.TryFinalizeCommit(height) + cs.tryFinalizeCommit(height) }() // SANITY CHECK - hash, partsHeader, ok := cs.Precommits.TwoThirdsMajority() + hash, partsHeader, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() if !ok { panic("RunActionCommit() expects +2/3 precommits") } @@ -815,17 +824,25 @@ func (cs *ConsensusState) EnterCommit(height uint) { } else { // We just need to keep waiting. } - } else { - // We have the block, so sign a Commit-vote. - cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts) } } // If we have the block AND +2/3 commits for it, finalize. -func (cs *ConsensusState) TryFinalizeCommit(height uint) { - if cs.ProposalBlock.HashesTo(hash) && cs.Commits.HasTwoThirdsMajority() { - go cs.FinalizeCommit(height) +func (cs *ConsensusState) tryFinalizeCommit(height uint) { + // SANITY CHECK + if cs.Height != height { + panic(Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) } + // END SANITY CHECK + + hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() + if !ok || len(hash) == 0 { + return + } + if !cs.ProposalBlock.HashesTo(hash) { + return + } + go cs.FinalizeCommit(height) } // Increment height and goto RoundStepNewHeight @@ -838,7 +855,7 @@ func (cs *ConsensusState) FinalizeCommit(height uint) { return } - hash, header, ok := cs.Commits.TwoThirdsMajority() + hash, header, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() // SANITY CHECK if !ok { @@ -857,26 +874,19 @@ func (cs *ConsensusState) FinalizeCommit(height uint) { log.Debug(Fmt("Finalizing commit of block: %v", cs.ProposalBlock)) // We have the block, so stage/save/commit-vote. - cs.saveBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) - cs.commitVoteBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Commits) - // Fire off event - go func() { - cs.evsw.FireEvent(types.EventStringNewBlock(), cs.ProposalBlock) - cs.evc.Flush() - }(cs.ProposalBlock) - + cs.saveBlock(cs.ProposalBlock, cs.ProposalBlockParts, cs.Votes.Precommits(cs.Round)) // Increment height. cs.updateToState(cs.stagedState, true) - + // cs.StartTime is already set. + // Schedule Round0 to start soon. + go cs.scheduleRound0(height + 1) // If we're unbonded, broadcast RebondTx. cs.maybeRebond() // By here, // * cs.Height has been increment to height+1 // * cs.Step is now RoundStepNewHeight - // * cs.StartTime is set to when we should start round0. - // Start round 0 when cs.StartTime. - go cs.scheduleRound0(height) + // * cs.StartTime is set to when we will start round0. return } @@ -903,7 +913,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. if proposal.POLRound != -1 && - (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { + (proposal.POLRound < 0 || proposal.Round <= uint(proposal.POLRound)) { return ErrInvalidProposalPOLRound } @@ -913,7 +923,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } cs.Proposal = proposal - cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockParts) + cs.ProposalBlockParts = types.NewPartSetFromHeader(proposal.BlockPartsHeader) return nil } @@ -944,23 +954,23 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty if cs.Step == RoundStepPropose && cs.isProposalComplete() { go cs.EnterPrevote(height, round) } else if cs.Step == RoundStepCommit { - cs.TryFinalizeCommit(height) + cs.tryFinalizeCommit(height) } return true, err } return true, nil } -func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() - return cs.addVote(address, vote) + return cs.addVote(address, vote, peerKey) } //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { // A precommit for the previous height? if vote.Height+1 == cs.Height && vote.Type == types.VoteTypePrecommit { added, index, err = cs.LastCommit.AddByAddress(address, vote) @@ -972,7 +982,8 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, // A prevote/precommit for this height? if vote.Height == cs.Height { - added, index, err = cs.Votes.AddByAddress(address, vote) + height := cs.Height + added, index, err = cs.Votes.AddByAddress(address, vote, peerKey) if added { switch vote.Type { case types.VoteTypePrevote: @@ -995,7 +1006,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, cs.EnterPrevoteWait(height, cs.Round) }() } - } else if cs.Proposal != nil && cs.Proposal.POLRound == vote.Round { + } else if cs.Proposal != nil && cs.Proposal.POLRound >= 0 && uint(cs.Proposal.POLRound) == vote.Round { if cs.isProposalComplete() { go cs.EnterPrevote(height, cs.Round) } @@ -1017,7 +1028,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, // If hash is block, goto Commit go func() { cs.EnterNewRound(height, vote.Round) - cs.EnterCommit(height, vote.Round) + cs.EnterCommit(height) }() } } else if cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { @@ -1035,7 +1046,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote) (added bool, go cs.EnterNewRound(height, cs.Round+1) } else { // If hash is block, goto Commit - go cs.EnterCommit(height, cs.Round) + go cs.EnterCommit(height) } } else if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { // Goto PrecommitWait @@ -1098,8 +1109,8 @@ func (cs *ConsensusState) signAddVote(type_ byte, hash []byte, header types.Part } err := cs.privValidator.SignVote(cs.state.ChainID, vote) if err == nil { - log.Info("Signed and added vote", "height", cs.Height, "round", cs.Round, "vote", vote) - cs.addVote(cs.privValidator.Address, vote) + _, _, err := cs.addVote(cs.privValidator.Address, vote, "") + log.Info("Signed and added vote", "height", cs.Height, "round", cs.Round, "vote", vote, "error", err) return vote } else { log.Warn("Error signing vote", "height", cs.Height, "round", cs.Round, "vote", vote, "error", err) @@ -1127,6 +1138,12 @@ func (cs *ConsensusState) saveBlock(block *types.Block, blockParts *types.PartSe // Update mempool. cs.mempoolReactor.Mempool.ResetForBlockAndState(block, cs.stagedState) + // Fire off event + go func(block *types.Block) { + cs.evsw.FireEvent(types.EventStringNewBlock(), block) + cs.evc.Flush() + }(block) + } // implements events.Eventable diff --git a/consensus/state_test.go b/consensus/state_test.go index 48e8bb50..599d3c39 100644 --- a/consensus/state_test.go +++ b/consensus/state_test.go @@ -1,28 +1,26 @@ package consensus import ( - "bytes" "testing" _ "github.com/tendermint/tendermint/config/tendermint_test" - "github.com/tendermint/tendermint/types" ) -func TestRunActionProposeNoPrivValidator(t *testing.T) { +func TestEnterProposeNoPrivValidator(t *testing.T) { cs, _ := randConsensusState() - cs.RunActionPropose(1, 0) + cs.EnterPropose(1, 0) rs := cs.GetRoundState() if rs.Proposal != nil { t.Error("Expected to make no proposal, since no privValidator") } } -func TestRunActionPropose(t *testing.T) { +func TestEnterPropose(t *testing.T) { cs, privValidators := randConsensusState() val0 := privValidators[0] cs.SetPrivValidator(val0) - cs.RunActionPropose(1, 0) + cs.EnterPropose(1, 0) rs := cs.GetRoundState() // Check that Proposal, ProposalBlock, ProposalBlockParts are set. diff --git a/consensus/types/proposal_test.go b/consensus/types/proposal_test.go index 5cc4a5f3..9e68789c 100644 --- a/consensus/types/proposal_test.go +++ b/consensus/types/proposal_test.go @@ -11,15 +11,16 @@ import ( func TestProposalSignable(t *testing.T) { proposal := &Proposal{ - Height: 12345, - Round: 23456, - BlockParts: types.PartSetHeader{111, []byte("blockparts")}, - POLParts: types.PartSetHeader{222, []byte("polparts")}, - Signature: nil, + Height: 12345, + Round: 23456, + BlockPartsHeader: types.PartSetHeader{111, []byte("blockparts")}, + POLRound: -1, + Signature: nil, } signBytes := account.SignBytes(config.GetString("chain_id"), proposal) signStr := string(signBytes) - expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_parts":{"hash":"706F6C7061727473","total":222},"round":23456}}`, + + expected := Fmt(`{"chain_id":"%s","proposal":{"block_parts_header":{"hash":"626C6F636B7061727473","total":111},"height":12345,"pol_round":-1,"round":23456}}`, config.GetString("chain_id")) if signStr != expected { t.Errorf("Got unexpected sign string for SendTx. Expected:\n%v\nGot:\n%v", expected, signStr) diff --git a/consensus/vote_set.go b/consensus/vote_set.go index f287b354..2227cfe9 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -199,7 +199,7 @@ func (voteSet *VoteSet) HasTwoThirdsAny() bool { } voteSet.mtx.Lock() defer voteSet.mtx.Unlock() - return voteSet.totalBlockHashVotes > voteSet.valSet.TotalVotingPower()*2/3 + return voteSet.totalVotes > voteSet.valSet.TotalVotingPower()*2/3 } // Returns either a blockhash (or nil) that received +2/3 majority. @@ -215,6 +215,9 @@ func (voteSet *VoteSet) TwoThirdsMajority() (hash []byte, parts types.PartSetHea } func (voteSet *VoteSet) String() string { + if voteSet == nil { + return "nil-VoteSet" + } return voteSet.StringIndented("") } @@ -260,7 +263,7 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { if len(voteSet.maj23Hash) == 0 { panic("Cannot MakeValidation() unless a blockhash has +2/3") } - precommits := make([]types.Precommit, voteSet.valSet.Size()) + precommits := make([]*types.Vote, voteSet.valSet.Size()) voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { vote := voteSet.votes[valIndex] if vote == nil { @@ -272,31 +275,10 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { if !vote.BlockParts.Equals(voteSet.maj23Parts) { return false } - precommits[valIndex] = types.Precommit{val.Address, vote.Signature} + precommits[valIndex] = vote return false }) return &types.Validation{ - Round: voteSet.round, Precommits: precommits, } } - -// XXX -func VoteSetFromValidation(validation *types.Validation) *VoteSet { - lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) - seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) - for idx, precommit := range seenValidation.Precommits { - precommitVote := &types.Vote{ - Height: state.LastBlockHeight, - Round: seenValidation.Round, - Type: types.VoteTypePrecommit, - BlockHash: state.LastBlockHash, - BlockParts: state.LastBlockParts, - Signature: precommit.Signature, - } - added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) - if !added || err != nil { - panic(Fmt("Failed to reconstruct LastPrecommits: %v", err)) - } - } -} diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index 56e33882..e794ef29 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -4,11 +4,9 @@ import ( "bytes" "strconv" "testing" - "time" "github.com/tendermint/tendermint/account" . "github.com/tendermint/tendermint/common" - "github.com/tendermint/tendermint/consensus" nm "github.com/tendermint/tendermint/node" "github.com/tendermint/tendermint/p2p" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -79,8 +77,7 @@ func init() { priv.SetFile(config.GetString("priv_validator_file")) priv.Save() - consensus.RoundDuration0 = 2 * time.Second - consensus.RoundDurationDelta = 1 * time.Second + // TODO: change consensus/state.go timeouts to be shorter // start a node ready := make(chan struct{}) diff --git a/state/state_test.go b/state/state_test.go index 9e5219dd..6a221977 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -72,6 +72,9 @@ func TestCopyState(t *testing.T) { } func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []types.Tx) *types.Block { + if validation == nil { + validation = &types.Validation{} + } block := &types.Block{ Header: &types.Header{ ChainID: state.ChainID, @@ -83,7 +86,7 @@ func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []t LastBlockParts: state.LastBlockParts, StateHash: nil, }, - Validation: validation, + LastValidation: validation, Data: &types.Data{ Txs: txs, }, @@ -628,13 +631,9 @@ func TestAddValidator(t *testing.T) { privValidators[0].SignVote(s0.ChainID, precommit0) block1 := makeBlock(t, s0, - types.Validation{ - Round: 0, - Precommits: []types.Precommit{ - types.Precommit{ - Address: privValidators[0].Address, - Signature: precommit0.Signature, - }, + &types.Validation{ + Precommits: []*types.Vote{ + precommit0, }, }, nil, ) diff --git a/types/block.go b/types/block.go index 59e26a80..b149c44e 100644 --- a/types/block.go +++ b/types/block.go @@ -208,6 +208,10 @@ func (v *Validation) ValidateBasic() error { } height, round := v.Height(), v.Round() for _, precommit := range v.Precommits { + // It's OK for precommits to be missing. + if precommit == nil { + continue + } // Ensure that all votes are precommits if precommit.Type != VoteTypePrecommit { return fmt.Errorf("Invalid validation vote. Expected precommit, got %v", @@ -229,10 +233,9 @@ func (v *Validation) ValidateBasic() error { func (v *Validation) Hash() []byte { if v.hash == nil { - bs := make([]interface{}, 1+len(v.Precommits)) - bs[0] = v.Round + bs := make([]interface{}, len(v.Precommits)) for i, precommit := range v.Precommits { - bs[1+i] = precommit + bs[i] = precommit } v.hash = merkle.SimpleHashFromBinaries(bs) } From 4d5fda7516026da5879929dfeb663ef74d955d32 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 24 Jun 2015 14:43:04 -0700 Subject: [PATCH 11/24] state machine cleanup --- consensus/state.go | 73 +++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 4fd5b183..eac33f1b 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -5,59 +5,64 @@ Consensus State Machine Overview: * NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). * To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. * During NewHeight/NewRound/Propose/Prevote/Precommit: - * Nodes gossip the locked proposal, if locked on a proposal. - * Nodes gossip the proposal proposed by the designated proposer for that round. + * Nodes gossip the proposal block proposed by the designated proposer for that round. * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 for catch-up) + * Nodes also gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. * Upon each state transition, the height/round/step is broadcast to neighboring peers. -* The set of +2/3 of precommits from the same round for a block "commits the block". +* The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. +* A block contains the last block's Validation, which includes the Commit precommits. + While all the precommits in the Validation are from the same height & round (ordered by validator index), + some precommits may be nil (if the validator's precommit vote didn't reach the proposer in time), + or some precommits may be for different blockhashes for the last block hash (which is fine). -* NewRound: - * Set up new round. --> Then, goto Propose +* NewRound(height:H,round:R): + * Set up new round. --> goto Propose(H,R) + * NOTE: Not much happens in this step. It exists for clarity. -* Propose: +* Propose(height:H,round:R): * Upon entering Propose: - * The designated proposer proposes a block. + * The designated proposer proposes a block for (H,R). * The Propose step ends: - * After `timeoutPropose` after entering Propose. --> Then, goto Prevote - * After receiving proposal block and POL prevotes are ready. --> Then, goto Prevote - * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round - * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round - * After +2/3 precommits received for a particular block. --> Then, goto Commit + * After `timeoutPropose` after entering Propose. --> goto Prevote(H,R) + * After receiving proposal block and all POL prevotes. --> goto Prevote(H,R) + * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) + * After +2/3 precommits received for a particular block. --> goto Commit(H) -* Prevote: +* Prevote(height:H,round:R): * Upon entering Prevote, each validator broadcasts its prevote vote. * If the validator is locked on a block, it prevotes that. - * Else, if the proposed block from the previous step is good, it prevotes that. + * Else, if the proposed block from Propose(H,R) is good, it prevotes that. * Else, if the proposal is invalid or wasn't received on time, it prevotes . * The Prevote step ends: - * After +2/3 prevotes for a particular block or . --> Then, goto Precommit - * After `timeoutPrevote` after receiving any +2/3 prevotes. --> Then, goto Precommit - * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round - * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round - * After +2/3 precommits received for a particular block. --> Then, goto Commit + * After +2/3 prevotes for a particular block or . --> goto Precommit(H,R) + * After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto Precommit(H,R) + * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) + * After +2/3 precommits received for a particular block. --> goto Commit(H) -* Precommit: +* Precommit(height:H,round:R): * Upon entering Precommit, each validator broadcasts its precommit vote. - * If the validator had seen +2/3 of prevotes for a particular block, + * If the validator had seen +2/3 of prevotes for a particular block from Prevote(H,R), it locks (changes lock to) that block and precommits that block. - * Else, if the validator had seen +2/3 of prevotes for nil, it unlocks and precommits . - * Else, if +2/3 of prevotes for a particular block or nil is not received on time, + * Else, if the validator had seen +2/3 of prevotes for , it unlocks and precommits . + * Else, if +2/3 of prevotes for a particular block or is not received on time, it precommits what it's locked on, or . * The Precommit step ends: - * After +2/3 precommits for a particular block. --> Then, goto Commit - * After +2/3 precommits for . --> Then, goto NewRound next round - * After `timeoutPrecommit` after receiving any +2/3 precommits. --> Then, goto NewRound next round - * After any +2/3 prevotes received for the next round. --> Then, goto Prevote next round - * After any +2/3 precommits received for the next round. --> Then, goto Precommit next round + * After +2/3 precommits for a particular block. --> goto Commit(H) + * After +2/3 precommits for . --> goto NewRound(H,R+1) + * After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto NewRound(H,R+1) + * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) -* Commit: +* Commit(height:H): * Set CommitTime = now - * Wait until block is received, then goto NewHeight + * Wait until block is received. --> goto NewHeight(H+1) -* NewHeight: - * Upon entering NewHeight, - * Move Precommits to LastCommit and increment height. - * Wait until `CommitTime+timeoutCommit` to receive straggler commits. --> Then, goto NewRound round 0 +* NewHeight(height:H): + * Move Precommits to LastCommit and increment height. + * Set StartTime = CommitTime+timeoutCommit + * Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0) * Proof of Safety: If a good validator commits at round R, it's because it saw +2/3 of precommits for round R. From d05276ee875e7e1fc0f896c93fd290b91c0cd68a Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 24 Jun 2015 17:05:52 -0700 Subject: [PATCH 12/24] wordings and clarifications, unnecessary code uncommenting --- consensus/height_vote_set.go | 27 +++---- consensus/state.go | 139 +++++++++++++++++++---------------- 2 files changed, 90 insertions(+), 76 deletions(-) diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index ebd94820..1377c67d 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -18,7 +18,8 @@ type RoundVoteSet struct { Keeps track of all VoteSets from round 0 to round 'round'. Also keeps track of up to one RoundVoteSet greater than -'round' from each peer, to facilitate fast-forward syncing. +'round' from each peer, to facilitate catchup syncing of commits. + A commit is +2/3 precommits for a block at a round, but which round is not known in advance, so when a peer provides a precommit for a round greater than mtx.round, @@ -29,18 +30,18 @@ type HeightVoteSet struct { height uint valSet *sm.ValidatorSet - mtx sync.Mutex - round uint // max tracked round - roundVoteSets map[uint]RoundVoteSet // keys: [0...round] - peerFastForward map[string]uint // keys: peer.Key; values: round + mtx sync.Mutex + round uint // max tracked round + roundVoteSets map[uint]RoundVoteSet // keys: [0...round] + peerCatchupRounds map[string]uint // keys: peer.Key; values: round } func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { hvs := &HeightVoteSet{ - height: height, - valSet: valSet, - roundVoteSets: make(map[uint]RoundVoteSet), - peerFastForward: make(map[string]uint), + height: height, + valSet: valSet, + roundVoteSets: make(map[uint]RoundVoteSet), + peerCatchupRounds: make(map[string]uint), } hvs.addRound(0) hvs.round = 0 @@ -66,7 +67,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) { } for r := hvs.round + 1; r <= round; r++ { if _, ok := hvs.roundVoteSets[r]; ok { - continue // Already exists because peerFastForward. + continue // Already exists because peerCatchupRounds. } hvs.addRound(round) } @@ -92,9 +93,9 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey defer hvs.mtx.Unlock() voteSet := hvs.getVoteSet(vote.Round, vote.Type) if voteSet == nil { - if _, ok := hvs.peerFastForward[peerKey]; !ok { + if _, ok := hvs.peerCatchupRounds[peerKey]; !ok { hvs.addRound(vote.Round) - hvs.peerFastForward[peerKey] = vote.Round + hvs.peerCatchupRounds[peerKey] = vote.Round } else { // Peer has sent a vote that does not match our round, // for more than one round. Bad peer! @@ -160,7 +161,7 @@ func (hvs *HeightVoteSet) StringIndented(indent string) string { voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() vsStrings = append(vsStrings, voteSetString) } - // all other peer fast-forward rounds + // all other peer catchup rounds for round, roundVoteSet := range hvs.roundVoteSets { if round <= hvs.round { continue diff --git a/consensus/state.go b/consensus/state.go index eac33f1b..34181036 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1,19 +1,31 @@ /* -Consensus State Machine Overview: + Consensus State Machine Overview: -* NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). -* To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. -* During NewHeight/NewRound/Propose/Prevote/Precommit: - * Nodes gossip the proposal block proposed by the designated proposer for that round. - * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 for catch-up) - * Nodes also gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. -* Upon each state transition, the height/round/step is broadcast to neighboring peers. -* The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. -* A block contains the last block's Validation, which includes the Commit precommits. - While all the precommits in the Validation are from the same height & round (ordered by validator index), - some precommits may be nil (if the validator's precommit vote didn't reach the proposer in time), - or some precommits may be for different blockhashes for the last block hash (which is fine). + NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). + + To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. + + During NewHeight/NewRound/Propose/Prevote/Precommit: + * Nodes gossip the proposal block proposed by the designated proposer for that round. + * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) + * Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. + * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup) + + Upon each state transition, the height/round/step is broadcast to neighboring peers. + + The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. + + A block contains the last block's Validation, which includes the Commit precommits. + While all the precommits in the Validation are from the same height & round (ordered by validator index), + some precommits may be (if the validator's precommit vote didn't reach the proposer in time), + or some precommits may be for different blockhashes for the last block hash (which is fine). + + Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for + some block or at some round. + + POL = Proof-of-Lock = +2/3 prevotes for block B or for (H,R) + lockRound < POLRound <= unlockOrChangeLockRound * NewRound(height:H,round:R): * Set up new round. --> goto Propose(H,R) @@ -273,15 +285,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) for idx, precommit := range seenValidation.Precommits { - precommitVote := &types.Vote{ - Height: state.LastBlockHeight, - Round: seenValidation.Round(), - Type: types.VoteTypePrecommit, - BlockHash: state.LastBlockHash, - BlockParts: state.LastBlockParts, - Signature: precommit.Signature, - } - added, _, err := lastPrecommits.AddByIndex(uint(idx), precommitVote) + added, _, err := lastPrecommits.AddByIndex(uint(idx), precommit) if !added || err != nil { panic(Fmt("Failed to reconstruct LastCommit: %v", err)) } @@ -320,11 +324,12 @@ func (cs *ConsensusState) Start() { } } +// EnterNewRound(height, 0) at cs.StartTime. func (cs *ConsensusState) scheduleRound0(height uint) { - log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) + //log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) sleepDuration := cs.StartTime.Sub(time.Now()) go func() { - if sleepDuration > 0 { + if 0 < sleepDuration { time.Sleep(sleepDuration) } cs.EnterNewRound(height, 0) @@ -346,7 +351,7 @@ func (cs *ConsensusState) IsStopped() bool { // The round becomes 0 and cs.Step becomes RoundStepNewHeight. func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { // SANITY CHECK - if contiguous && cs.Height > 0 && cs.Height != state.LastBlockHeight { + if contiguous && 0 < cs.Height && cs.Height != state.LastBlockHeight { panic(Fmt("updateToState() expected state height of %v but found %v", cs.Height, state.LastBlockHeight)) } @@ -357,6 +362,9 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { height := state.LastBlockHeight + 1 // next desired block height lastPrecommits := (*VoteSet)(nil) if contiguous && cs.Votes != nil { + if !cs.Votes.Precommits(cs.Round).HasTwoThirdsMajority() { + panic("updateToState(state, true) called but last Precommit round didn't have +2/3") + } lastPrecommits = cs.Votes.Precommits(cs.Round) } @@ -424,9 +432,9 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { //----------------------------------------------------------------------------- -// Enter: +2/3 precommits for nil from previous round -// Enter: `timeoutPrecommits` after any +2/3 precommits -// Enter: `commitTime+timeoutCommit` from NewHeight +// Enter: +2/3 precommits for nil from (height,round-1) +// Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) +// Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height) // NOTE: cs.StartTime was already set for height. func (cs *ConsensusState) EnterNewRound(height uint, round uint) { cs.mtx.Lock() @@ -453,17 +461,17 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) { cs.Proposal = nil cs.ProposalBlock = nil cs.ProposalBlockParts = nil - cs.Votes.SetRound(round + 1) // track next round. + cs.Votes.SetRound(round + 1) // also track next round (round+1) to allow round-skipping // Immediately go to EnterPropose. go cs.EnterPropose(height, round) } -// Enter: from NewRound. +// Enter: from NewRound(height,round). func (cs *ConsensusState) EnterPropose(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPropose) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) { log.Debug(Fmt("EnterPropose(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -486,21 +494,21 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { cs.EnterPrevote(height, round) }() - // Nothing to do if it's not our turn. + // Nothing more to do if we're not a validator if cs.privValidator == nil { return } - // See if it is our turn to propose if !bytes.Equal(cs.Validators.Proposer().Address, cs.privValidator.Address) { log.Debug("EnterPropose: Not our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) - return } else { log.Debug("EnterPropose: Our turn to propose", "proposer", cs.Validators.Proposer().Address, "privValidator", cs.privValidator) + cs.decideProposal(height, round) } +} - // We are going to propose a block. - +// Decides on the next proposal and sets them onto cs.Proposal* +func (cs *ConsensusState) decideProposal(height uint, round uint) { var block *types.Block var blockParts *types.PartSet @@ -514,21 +522,23 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { } // Make proposal - proposal := NewProposal(cs.Height, cs.Round, blockParts.Header(), cs.Votes.POLRound()) + proposal := NewProposal(height, round, blockParts.Header(), cs.Votes.POLRound()) err := cs.privValidator.SignProposal(cs.state.ChainID, proposal) if err == nil { - log.Info("Signed and set proposal", "height", cs.Height, "round", cs.Round, "proposal", proposal) + log.Info("Signed and set proposal", "height", height, "round", round, "proposal", proposal) log.Debug(Fmt("Signed and set proposal block: %v", block)) // Set fields cs.Proposal = proposal cs.ProposalBlock = block cs.ProposalBlockParts = blockParts } else { - log.Warn("EnterPropose: Error signing proposal", "height", cs.Height, "round", cs.Round, "error", err) + log.Warn("EnterPropose: Error signing proposal", "height", height, "round", round, "error", err) } } +// Returns true if the proposal block is complete && +// (if POLRound was proposed, we have +2/3 prevotes from there). func (cs *ConsensusState) isProposalComplete() bool { if cs.Proposal == nil || cs.ProposalBlock == nil { return false @@ -585,32 +595,30 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts return block, blockParts } -// Enter: `timeoutPropose` after start of Propose. +// Enter: `timeoutPropose` after entering Propose. // Enter: proposal block and POL is ready. -// Enter: any +2/3 prevotes for next round. -// Prevote for LockedBlock if we're locked, or ProposealBlock if valid. +// Enter: any +2/3 prevotes for future round. +// Prevote for LockedBlock if we're locked, or ProposalBlock if valid. // Otherwise vote nil. func (cs *ConsensusState) EnterPrevote(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevote) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevote <= cs.Step) { log.Debug(Fmt("EnterPrevote(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } - defer func() { - // Done EnterPrevote: - cs.Round = round - cs.Step = RoundStepPrevote - cs.newStepCh <- cs.getRoundState() - // Maybe immediately go to EnterPrevoteWait. - if cs.Votes.Prevotes(round).HasTwoThirdsAny() { - go cs.EnterPrevoteWait(height, round) - } - }() - // Sign and broadcast vote as necessary cs.doPrevote(height, round) + + // Done EnterPrevote: + cs.Round = round + cs.Step = RoundStepPrevote + cs.newStepCh <- cs.getRoundState() + /* This isn't necessary because addVote() does it for us. + if cs.Votes.Prevotes(round).HasTwoThirdsAny() { + go cs.EnterPrevoteWait(height, round) + }*/ } func (cs *ConsensusState) doPrevote(height uint, round uint) { @@ -646,7 +654,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) { func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrevoteWait) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevoteWait <= cs.Step) { log.Debug(Fmt("EnterPrevoteWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -675,7 +683,7 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommit) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) { log.Debug(Fmt("EnterPrecommit(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -685,10 +693,10 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { cs.Round = round cs.Step = RoundStepPrecommit cs.newStepCh <- cs.getRoundState() - // Maybe immediately go to EnterPrecommitWait. + /* This isn't necessary because addVote() does it for us. if cs.Votes.Precommits(round).HasTwoThirdsAny() { go cs.EnterPrecommitWait(height, round) - } + }*/ }() hash, partsHeader, ok := cs.Votes.Prevotes(round).TwoThirdsMajority() @@ -741,7 +749,11 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { } // Otherwise, we need to fetch the +2/3 prevoted block. - // We don't have the block yet so we can't lock/precommit it. + // Unlock and precommit nil. + // The +2/3 prevotes for this round is the POL for our unlock. + if cs.Votes.POLRound() < round { + panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound())) + } cs.LockedBlock = nil cs.LockedBlockParts = nil if !cs.ProposalBlockParts.HasHeader(partsHeader) { @@ -756,7 +768,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Round > round || (cs.Round == round && cs.Step >= RoundStepPrecommitWait) { + if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommitWait <= cs.Step) { log.Debug(Fmt("EnterPrecommitWait(%v/%v): Invalid args. Current step: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step)) return } @@ -784,7 +796,7 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { func (cs *ConsensusState) EnterCommit(height uint) { cs.mtx.Lock() defer cs.mtx.Unlock() - if cs.Height != height || cs.Step >= RoundStepCommit { + if cs.Height != height || RoundStepCommit <= cs.Step { log.Debug(Fmt("EnterCommit(%v): Invalid args. Current step: %v/%v/%v", height, cs.Height, cs.Round, cs.Step)) return } @@ -912,7 +924,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } // We don't care about the proposal if we're already in RoundStepCommit. - if cs.Step >= RoundStepCommit { + if RoundStepCommit <= cs.Step { return nil } @@ -959,6 +971,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty if cs.Step == RoundStepPropose && cs.isProposalComplete() { go cs.EnterPrevote(height, round) } else if cs.Step == RoundStepCommit { + /// XXX How about, EnterCommit()? cs.tryFinalizeCommit(height) } return true, err @@ -1011,7 +1024,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri cs.EnterPrevoteWait(height, cs.Round) }() } - } else if cs.Proposal != nil && cs.Proposal.POLRound >= 0 && uint(cs.Proposal.POLRound) == vote.Round { + } else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round { if cs.isProposalComplete() { go cs.EnterPrevote(height, cs.Round) } From 44216ab48158171ffe49af37a4fc6c21af64d625 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Wed, 24 Jun 2015 18:51:14 -0700 Subject: [PATCH 13/24] Unlocking from POL complete --- consensus/state.go | 148 ++++++++++++++++++++++----------------------- 1 file changed, 71 insertions(+), 77 deletions(-) diff --git a/consensus/state.go b/consensus/state.go index 34181036..a1be04d1 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -7,8 +7,8 @@ To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. During NewHeight/NewRound/Propose/Prevote/Precommit: - * Nodes gossip the proposal block proposed by the designated proposer for that round. - * Nodes gossip prevotes/precommits for rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) + * Nodes gossip the proposal block proposed by the designated proposer at round. + * Nodes gossip prevotes/precommits at rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) * Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup) @@ -24,7 +24,7 @@ Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for some block or at some round. - POL = Proof-of-Lock = +2/3 prevotes for block B or for (H,R) + POL = Proof-of-Lock = +2/3 prevotes for block B (or +2/3 prevotes for ) at (H,R) lockRound < POLRound <= unlockOrChangeLockRound * NewRound(height:H,round:R): @@ -33,12 +33,12 @@ * Propose(height:H,round:R): * Upon entering Propose: - * The designated proposer proposes a block for (H,R). + * The designated proposer proposes a block at (H,R). * The Propose step ends: * After `timeoutPropose` after entering Propose. --> goto Prevote(H,R) * After receiving proposal block and all POL prevotes. --> goto Prevote(H,R) - * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) - * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) + * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1) * After +2/3 precommits received for a particular block. --> goto Commit(H) * Prevote(height:H,round:R): @@ -49,8 +49,8 @@ * The Prevote step ends: * After +2/3 prevotes for a particular block or . --> goto Precommit(H,R) * After `timeoutPrevote` after receiving any +2/3 prevotes. --> goto Precommit(H,R) - * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) - * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) + * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1) * After +2/3 precommits received for a particular block. --> goto Commit(H) * Precommit(height:H,round:R): @@ -64,8 +64,8 @@ * After +2/3 precommits for a particular block. --> goto Commit(H) * After +2/3 precommits for . --> goto NewRound(H,R+1) * After `timeoutPrecommit` after receiving any +2/3 precommits. --> goto NewRound(H,R+1) - * After any +2/3 prevotes received for (H,R+1). --> goto Prevote(H,R+1) - * After any +2/3 precommits received for (H,R+1). --> goto Precommit(H,R+1) + * After any +2/3 prevotes received at (H,R+1). --> goto Prevote(H,R+1) + * After any +2/3 precommits received at (H,R+1). --> goto Precommit(H,R+1) * Commit(height:H): * Set CommitTime = now @@ -77,7 +77,7 @@ * Wait until `StartTime` to receive straggler commits. --> goto NewRound(H,0) * Proof of Safety: - If a good validator commits at round R, it's because it saw +2/3 of precommits for round R. + If a good validator commits at round R, it's because it saw +2/3 of precommits at round R. This implies that (assuming tolerance bounds) +1/3 of honest nodes are still locked at round R+1. These locked validators will remain locked until they see +2/3 prevote for something else, but this won't happen because +1/3 are locked and honest. @@ -199,10 +199,11 @@ type RoundState struct { Proposal *Proposal ProposalBlock *types.Block ProposalBlockParts *types.PartSet + LockedRound uint LockedBlock *types.Block LockedBlockParts *types.PartSet Votes *HeightVoteSet - LastCommit *VoteSet // Last precommits for Height-1 + LastCommit *VoteSet // Last precommits at Height-1 } func (rs *RoundState) String() string { @@ -217,6 +218,7 @@ func (rs *RoundState) StringIndented(indent string) string { %s Validators: %v %s Proposal: %v %s ProposalBlock: %v %v +%s LockedRound: %v %s LockedBlock: %v %v %s Votes: %v %s LastCommit: %v @@ -227,6 +229,7 @@ func (rs *RoundState) StringIndented(indent string) string { indent, rs.Validators.StringIndented(indent+" "), indent, rs.Proposal, indent, rs.ProposalBlockParts.StringShort(), rs.ProposalBlock.StringShort(), + indent, rs.LockedRound, indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), indent, rs.Votes.StringIndented(indent+" "), indent, rs.LastCommit.StringShort(), @@ -387,6 +390,7 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { cs.Proposal = nil cs.ProposalBlock = nil cs.ProposalBlockParts = nil + cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil cs.Votes = NewHeightVoteSet(height, validators) @@ -432,7 +436,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { //----------------------------------------------------------------------------- -// Enter: +2/3 precommits for nil from (height,round-1) +// Enter: +2/3 precommits for nil at (height,round-1) // Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) // Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height) // NOTE: cs.StartTime was already set for height. @@ -650,7 +654,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) { return } -// Enter: any +2/3 prevotes for next round. +// Enter: any +2/3 prevotes at next round. func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -719,6 +723,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { log.Info("EnterPrecommit: +2/3 prevoted for nil.") } else { log.Info("EnterPrecommit: +2/3 prevoted for nil. Unlocking") + cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil } @@ -742,6 +747,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { if err := cs.stageBlock(cs.ProposalBlock, cs.ProposalBlockParts); err != nil { panic(Fmt("EnterPrecommit: +2/3 prevoted for an invalid block: %v", err)) } + cs.LockedRound = round cs.LockedBlock = cs.ProposalBlock cs.LockedBlockParts = cs.ProposalBlockParts cs.signAddVote(types.VoteTypePrecommit, hash, partsHeader) @@ -751,9 +757,10 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { // Otherwise, we need to fetch the +2/3 prevoted block. // Unlock and precommit nil. // The +2/3 prevotes for this round is the POL for our unlock. - if cs.Votes.POLRound() < round { + if cs.Votes.POLRound() < int(round) { panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound())) } + cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil if !cs.ProposalBlockParts.HasHeader(partsHeader) { @@ -824,9 +831,11 @@ func (cs *ConsensusState) EnterCommit(height uint) { if cs.LockedBlock.HashesTo(hash) { cs.ProposalBlock = cs.LockedBlock cs.ProposalBlockParts = cs.LockedBlockParts + cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil } else { + cs.LockedRound = 0 cs.LockedBlock = nil cs.LockedBlockParts = nil } @@ -854,10 +863,10 @@ func (cs *ConsensusState) tryFinalizeCommit(height uint) { hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority() if !ok || len(hash) == 0 { - return + return // There was no +2/3 majority, or +2/3 was for . } if !cs.ProposalBlock.HashesTo(hash) { - return + return // We don't have the commit block. } go cs.FinalizeCommit(height) } @@ -964,19 +973,21 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty return added, err } if added && cs.ProposalBlockParts.IsComplete() { + // Added and completed! var n int64 var err error cs.ProposalBlock = binary.ReadBinary(&types.Block{}, cs.ProposalBlockParts.GetReader(), &n, &err).(*types.Block) log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash()) if cs.Step == RoundStepPropose && cs.isProposalComplete() { + // Move onto the next step go cs.EnterPrevote(height, round) } else if cs.Step == RoundStepCommit { - /// XXX How about, EnterCommit()? + // If we're waiting on the proposal block... cs.tryFinalizeCommit(height) } return true, err } - return true, nil + return added, nil } func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { @@ -1005,74 +1016,57 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri if added { switch vote.Type { case types.VoteTypePrevote: - log.Debug(Fmt("Added to prevotes: %v", cs.Votes.Prevotes(vote.Round).StringShort())) - if cs.Round < vote.Round && cs.Votes.Prevotes(vote.Round).HasTwoThirdsAny() { - // Goto to Prevote vote.Round. - go func() { - cs.EnterNewRound(height, vote.Round) - cs.EnterPrevote(height, vote.Round) - cs.EnterPrevoteWait(height, vote.Round) - }() - } else if cs.Round == vote.Round { - if cs.Votes.Prevotes(cs.Round).HasTwoThirdsMajority() { - // Goto Precommit, whether for block or nil. - go cs.EnterPrecommit(height, cs.Round) - } else if cs.Votes.Prevotes(cs.Round).HasTwoThirdsAny() { - // Goto PrevoteWait - go func() { - cs.EnterPrevote(height, cs.Round) - cs.EnterPrevoteWait(height, cs.Round) - }() + prevotes := cs.Votes.Prevotes(vote.Round) + log.Debug(Fmt("Added to prevotes: %v", prevotes.StringShort())) + // First, unlock if prevotes is a valid POL. + if cs.LockedBlock != nil && cs.LockedRound < vote.Round { + hash, _, ok := prevotes.TwoThirdsMajority() + if ok && !cs.LockedBlock.HashesTo(hash) { + log.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round) + cs.LockedRound = 0 + cs.LockedBlock = nil + cs.LockedBlockParts = nil } - } else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round { + } + if cs.Round <= vote.Round && prevotes.HasTwoThirdsAny() { + // Round-skip over to PrevoteWait or goto Precommit. + go func() { + if cs.Round < vote.Round { + cs.EnterNewRound(height, vote.Round) + } + if prevotes.HasTwoThirdsMajority() { + cs.EnterPrecommit(height, vote.Round) + } else { + cs.EnterPrevote(height, vote.Round) + cs.EnterPrevoteWait(height, vote.Round) + } + }() + } else if cs.Proposal != nil && + 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round { + // If the proposal is now complete, enter prevote of cs.Round. if cs.isProposalComplete() { go cs.EnterPrevote(height, cs.Round) } } case types.VoteTypePrecommit: - log.Debug(Fmt("Added to precommit: %v", cs.Votes.Precommits(vote.Round).StringShort())) - if cs.Round < vote.Round { - if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { - if len(hash) == 0 { - // This is weird, shouldn't happen - log.Warn("This is weird, why did we receive +2/3 of nil precommits?") - // Skip to Precommit of vote.Round - go func() { - cs.EnterNewRound(height, vote.Round) - cs.EnterPrecommit(height, vote.Round) - cs.EnterPrecommitWait(height, vote.Round) - }() - } else { - // If hash is block, goto Commit - go func() { - cs.EnterNewRound(height, vote.Round) - cs.EnterCommit(height) - }() - } - } else if cs.Votes.Precommits(vote.Round).HasTwoThirdsAny() { - // Skip to Precommit of vote.Round - go func() { + precommits := cs.Votes.Precommits(vote.Round) + log.Debug(Fmt("Added to precommit: %v", precommits.StringShort())) + if cs.Round <= vote.Round && precommits.HasTwoThirdsAny() { + go func() { + hash, _, ok := precommits.TwoThirdsMajority() + if ok && len(hash) == 0 { + cs.EnterNewRound(height, vote.Round+1) + return + } else if cs.Round < vote.Round { cs.EnterNewRound(height, vote.Round) + } + if ok { + cs.EnterCommit(height) + } else { cs.EnterPrecommit(height, vote.Round) cs.EnterPrecommitWait(height, vote.Round) - }() - } - } else if cs.Round == vote.Round { - if hash, _, ok := cs.Votes.Precommits(cs.Round).TwoThirdsMajority(); ok { - if len(hash) == 0 { - // If hash is nil, goto NewRound - go cs.EnterNewRound(height, cs.Round+1) - } else { - // If hash is block, goto Commit - go cs.EnterCommit(height) } - } else if cs.Votes.Precommits(cs.Round).HasTwoThirdsAny() { - // Goto PrecommitWait - go func() { - cs.EnterPrecommit(height, cs.Round) - cs.EnterPrecommitWait(height, cs.Round) - }() - } + }() } default: panic(Fmt("Unexpected vote type %X", vote.Type)) // Should not happen. From 32811c3f861e31788611a39e15f96d98589bce60 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 25 Jun 2015 12:52:16 -0700 Subject: [PATCH 14/24] comment cleanup --- consensus/reactor.go | 2 +- consensus/state.go | 53 ++++++++++++++++++++++++++++---------------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index c8cce2d7..9b515ac4 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -353,7 +353,7 @@ OUTER_LOOP: } // If the peer is on a previous height, help catch up. - if 0 < prs.Height && prs.Height < rs.Height { + if (0 < prs.Height) && (prs.Height < rs.Height) { //log.Debug("Data catchup", "height", rs.Height, "peerHeight", prs.Height, "peerProposalBlockParts", prs.ProposalBlockParts) if index, ok := prs.ProposalBlockParts.Not().PickRandom(); ok { // Ensure that the peer's PartSetHeader is correct diff --git a/consensus/state.go b/consensus/state.go index a1be04d1..cd22ac23 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -1,31 +1,35 @@ /* - Consensus State Machine Overview: + Consensus State Machine Overview: - NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). + NewHeight, NewRound, Propose, Prevote, Precommit represent state machine steps. (aka RoundStep). - To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. + To "prevote/precommit" something means to broadcast a prevote/precommit vote for something. - During NewHeight/NewRound/Propose/Prevote/Precommit: - * Nodes gossip the proposal block proposed by the designated proposer at round. - * Nodes gossip prevotes/precommits at rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) - * Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. - * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup) + During NewHeight/NewRound/Propose/Prevote/Precommit: + * Nodes gossip the proposal block proposed by the designated proposer at round. + * Nodes gossip prevotes/precommits at rounds [0...currentRound+1] (currentRound+1 to allow round-skipping) + * Nodes gossip prevotes for the proposal's POL (proof-of-lock) round if proposed. + * Nodes gossip to late nodes (lagging in height) with precommits of the commit round (aka catchup) - Upon each state transition, the height/round/step is broadcast to neighboring peers. + Upon each state transition, the height/round/step is broadcast to neighboring peers. - The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. + The set of +2/3 of precommits at the same round for the same block is called a Commit, or Validation. - A block contains the last block's Validation, which includes the Commit precommits. - While all the precommits in the Validation are from the same height & round (ordered by validator index), - some precommits may be (if the validator's precommit vote didn't reach the proposer in time), - or some precommits may be for different blockhashes for the last block hash (which is fine). + A block contains the last block's Validation, which includes the Commit precommits. + While all the precommits in the Validation are from the same height & round (ordered by validator index), + some precommits may be (if the validator's precommit vote didn't reach the proposer in time), + or some precommits may be for different blockhashes for the last block hash (which is fine). - Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for - some block or at some round. + Each unlock/change-of-lock should be justifiable by an POL where +2/3 prevoted for + some block or at some round. - POL = Proof-of-Lock = +2/3 prevotes for block B (or +2/3 prevotes for ) at (H,R) - lockRound < POLRound <= unlockOrChangeLockRound + POL = Proof-of-Lock = +2/3 prevotes for block B (or +2/3 prevotes for ) at (H,R) + lockRound < POLRound <= unlockOrChangeLockRound + + Without the POLRound <= unlockOrChangeLockRound condition, an unlock would be possible from a + future condition that hasn't happened yet, so it destroys deterministic accountability. + With lockRound < POLRound <= unlockOrChangeLockRound, blame can be shifted to lower rounds. * NewRound(height:H,round:R): * Set up new round. --> goto Propose(H,R) @@ -89,6 +93,13 @@ are fixed, so eventually we'll be able to "fully gossip" the block & POL. TODO: cap the block.size at something reasonable. Lemma 2: If a good node is at round R, neighboring good nodes will soon catch up to round R. + Lemma 3: If a node at (H,R) receives +2/3 prevotes for a block (or +2/3 for ) at (H,R+1), + it will enter NewRound(H,R+1). + Lemma 4: Terminal conditions imply the existence of deterministic accountability, for + a synchronous (fixed-duration) protocol extension (judgement). + TODO: define terminal conditions (fork and non-decision). + TODO: define new assumptions for the synchronous judgement period. + +-------------------------------------+ v |(Wait til `CommmitTime+timeoutCommit`) @@ -1019,7 +1030,11 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri prevotes := cs.Votes.Prevotes(vote.Round) log.Debug(Fmt("Added to prevotes: %v", prevotes.StringShort())) // First, unlock if prevotes is a valid POL. - if cs.LockedBlock != nil && cs.LockedRound < vote.Round { + // >> lockRound < POLRound <= unlockOrChangeLockRound (see spec) + // NOTE: If (lockRound < POLRound) but !(POLRound <= unlockOrChangeLockRound), + // we'll still EnterNewRound(H,vote.R) and EnterPrecommit(H,vote.R) to process it + // there. + if (cs.LockedBlock != nil) && (cs.LockedRound < vote.Round) && (vote.Round <= cs.Round) { hash, _, ok := prevotes.TwoThirdsMajority() if ok && !cs.LockedBlock.HashesTo(hash) { log.Info("Unlocking because of POL.", "lockedRound", cs.LockedRound, "POLRound", vote.Round) From 7a6b8944ab0f5d200f5f45d69bd98d12326a6d34 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 25 Jun 2015 14:05:18 -0700 Subject: [PATCH 15/24] consensus/reactor cleanup --- consensus/reactor.go | 57 +++++++++++++++++++++++--------------------- consensus/state.go | 6 ++--- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index 9b515ac4..22c4182c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -168,7 +168,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte ps.ApplyProposalPOLMessage(msg) case *BlockPartMessage: ps.SetHasProposalBlockPart(msg.Height, msg.Round, msg.Part.Proof.Index) - _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Round, msg.Part) + _, err = conR.conS.AddProposalBlockPart(msg.Height, msg.Part) default: log.Warn(Fmt("Unknown message type %v", reflect.TypeOf(msg))) } @@ -342,12 +342,12 @@ OUTER_LOOP: if index, ok := rs.ProposalBlockParts.BitArray().Sub(prs.ProposalBlockParts.Copy()).PickRandom(); ok { part := rs.ProposalBlockParts.GetPart(index) msg := &BlockPartMessage{ - Height: rs.Height, - Round: rs.Round, + Height: rs.Height, // This tells peer that this part applies to us. + Round: rs.Round, // This tells peer that this part applies to us. Part: part, } peer.Send(DataChannel, msg) - ps.SetHasProposalBlockPart(rs.Height, rs.Round, index) + ps.SetHasProposalBlockPart(prs.Height, prs.Round, index) continue OUTER_LOOP } } @@ -374,8 +374,8 @@ OUTER_LOOP: } // Send the part msg := &BlockPartMessage{ - Height: prs.Height, - Round: prs.Round, + Height: prs.Height, // Not our height, so it doesn't matter. + Round: prs.Round, // Not our height, so it doesn't matter. Part: part, } peer.Send(DataChannel, msg) @@ -389,13 +389,16 @@ OUTER_LOOP: } // If height and round don't match, sleep. - if rs.Height != prs.Height || rs.Round != prs.Round { + if (rs.Height != prs.Height) || (rs.Round != prs.Round) { //log.Debug("Peer Height|Round mismatch, sleeping", "peerHeight", prs.Height, "peerRound", prs.Round, "peer", peer) time.Sleep(peerGossipSleepDuration) continue OUTER_LOOP } // By here, height and round match. + // Proposal block parts were already matched and sent if any were wanted. + // (These can match on hash so the round doesn't matter) + // Now consider sending other things, like the Proposal itself. // Send Proposal && ProposalPOL BitArray? if rs.Proposal != nil && !prs.Proposal { @@ -406,8 +409,9 @@ OUTER_LOOP: ps.SetHasProposal(rs.Proposal) } // ProposalPOL. - // Must be in the same channel, sequential. - // That is, peer must receive ProposalMessage first. + // Peer must receive ProposalMessage first. + // rs.Proposal was validated, so rs.Proposal.POLRound <= rs.Round, + // so we definitely have rs.Votes.Prevotes(rs.Proposal.POLRound). if 0 <= rs.Proposal.POLRound { msg := &ProposalPOLMessage{ Height: rs.Height, @@ -530,11 +534,13 @@ OUTER_LOOP: // If peer is lagging by height 1, send LastCommit. if prs.Height != 0 && prs.Height == rs.Height-1 { if prs.Round == rs.LastCommit.Round() { + // NOTE: We prefer to use prs.Precommits if + // prs.Round matches prs.CatchupCommitRound. if trySendVote(rs.LastCommit, &prs.Precommits) { continue OUTER_LOOP } } else { - ps.SetCatchupCommitRound(prs.Height, rs.LastCommit.Round()) + ps.EnsureCatchupCommitRound(prs.Height, rs.LastCommit.Round()) if trySendVote(rs.LastCommit, &prs.CatchupCommit) { continue OUTER_LOOP } @@ -548,15 +554,8 @@ OUTER_LOOP: // which contains precommit signatures for prs.Height. validation := conR.blockStore.LoadBlockValidation(prs.Height) log.Debug("Loaded BlockValidation for catch-up", "height", prs.Height, "validation", validation) - // Peer's CatchupCommitRound should be -1 or equal to the validation's precommit rounds. - // If not, warn. - if prs.CatchupCommitRound == -1 { - ps.SetCatchupCommitRound(prs.Height, validation.Round()) - continue OUTER_LOOP // Get prs := ps.GetRoundState() again. - } else if prs.CatchupCommitRound != int(validation.Round()) { - log.Warn("Peer's CatchupCommitRound during catchup not equal to commit round", - "height", prs.Height, "validation", validation, "prs.CatchupCommitRound", prs.CatchupCommitRound) - } else if trySendPrecommitFromValidation(validation, &prs.CatchupCommit) { + ps.EnsureCatchupCommitRound(prs.Height, validation.Round()) + if trySendPrecommitFromValidation(validation, &prs.CatchupCommit) { continue OUTER_LOOP } } @@ -656,6 +655,8 @@ func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint } // prs: If given, will also update this PeerRoundState copy. +// NOTE: It's important to make sure that numValidators actually matches +// what the node sees as the number of validators for height. func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *PeerRoundState) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -683,9 +684,9 @@ func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *P if prs != nil { prs.Prevotes = ps.Prevotes prs.Precommits = ps.Precommits - prs.LastCommit = ps.LastCommit prs.CatchupCommit = ps.CatchupCommit prs.ProposalPOL = ps.ProposalPOL + prs.LastCommit = ps.LastCommit } } @@ -726,7 +727,8 @@ func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) } } -func (ps *PeerState) SetCatchupCommitRound(height, round uint) { +// NOTE: 'round' is what we know to be the commit round for height. +func (ps *PeerState) EnsureCatchupCommitRound(height, round uint) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -734,12 +736,10 @@ func (ps *PeerState) SetCatchupCommitRound(height, round uint) { return } if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != int(round) { - log.Warn("Conflicting CatchupCommitRound", - "height", height, - "orig", ps.CatchupCommitRound, - "new", round, - ) - // TODO think harder + panic(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round)) + } + if ps.CatchupCommitRound == int(round) { + return // Nothing to do! } ps.CatchupCommitRound = int(round) ps.CatchupCommit = nil @@ -773,6 +773,9 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun } if psHeight == msg.Height && psRound != msg.Round && int(msg.Round) == psCatchupCommitRound { // Peer caught up to CatchupCommitRound. + // Preserve psCatchupCommit! + // NOTE: We prefer to use prs.Precommits if + // pr.Round matches pr.CatchupCommitRound. ps.Precommits = psCatchupCommit } if psHeight != msg.Height { diff --git a/consensus/state.go b/consensus/state.go index cd22ac23..1811c117 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -499,7 +499,7 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { // If we already have the proposal + POL, then goto Prevote if cs.isProposalComplete() { - go cs.EnterPrevote(height, round) + go cs.EnterPrevote(height, cs.Round) } }() @@ -965,7 +965,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } // NOTE: block is not necessarily valid. -func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *types.Part) (added bool, err error) { +func (cs *ConsensusState) AddProposalBlockPart(height uint, part *types.Part) (added bool, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -991,7 +991,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, round uint, part *ty log.Debug("Received complete proposal", "hash", cs.ProposalBlock.Hash()) if cs.Step == RoundStepPropose && cs.isProposalComplete() { // Move onto the next step - go cs.EnterPrevote(height, round) + go cs.EnterPrevote(height, cs.Round) } else if cs.Step == RoundStepCommit { // If we're waiting on the proposal block... cs.tryFinalizeCommit(height) From 347276ecdbfcc2bfd71ab6eebc26b91c42a2f2a6 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 25 Jun 2015 14:08:05 -0700 Subject: [PATCH 16/24] bump version and testnet --- config/tendermint/config.go | 4 ++-- config/tendermint_test/config.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/tendermint/config.go b/config/tendermint/config.go index dc07da2e..aae2ccbf 100644 --- a/config/tendermint/config.go +++ b/config/tendermint/config.go @@ -57,8 +57,8 @@ func GetConfig(rootDir string) cfg.Config { if mapConfig.IsSet("version") { Exit("Cannot set 'version' via config.toml") } - mapConfig.SetDefault("chain_id", "tendermint_testnet_5") - mapConfig.SetDefault("version", "0.3.0") // JAE: changed merkle tree persistence format for merkle proofs. + mapConfig.SetDefault("chain_id", "tendermint_testnet_6") + mapConfig.SetDefault("version", "0.4.0") // JAE: async consensus! mapConfig.SetDefault("genesis_file", rootDir+"/genesis.json") mapConfig.SetDefault("moniker", "anonymous") mapConfig.SetDefault("node_laddr", "0.0.0.0:46656") diff --git a/config/tendermint_test/config.go b/config/tendermint_test/config.go index f3b6377f..b044548c 100644 --- a/config/tendermint_test/config.go +++ b/config/tendermint_test/config.go @@ -67,7 +67,7 @@ func GetConfig(rootDir string) cfg.Config { Exit("Cannot set 'version' via config.toml") } mapConfig.SetDefault("chain_id", "tendermint_test") - mapConfig.SetDefault("version", "0.3.0") + mapConfig.SetDefault("version", "0.4.0") mapConfig.SetDefault("genesis_file", rootDir+"/genesis.json") mapConfig.SetDefault("moniker", "anonymous") mapConfig.SetDefault("node_laddr", "0.0.0.0:36656") From a55950e1d0bdd414188f43529b2a9e24dcc79c1d Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 25 Jun 2015 18:42:17 -0700 Subject: [PATCH 17/24] make will install --- Makefile | 9 +++++++-- cmd/tendermint/main.go | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 38ff4212..d0694bd5 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,11 @@ -.PHONY: get_deps build all list_deps +.PHONY: get_deps build all list_deps install -all: build +all: install + +install: + go install github.com/tendermint/tendermint/cmd/tendermint + go install github.com/tendermint/tendermint/cmd/barak + go install github.com/tendermint/tendermint/cmd/debora build: go build -o build/tendermint github.com/tendermint/tendermint/cmd/tendermint diff --git a/cmd/tendermint/main.go b/cmd/tendermint/main.go index 628ad618..f714bbe6 100644 --- a/cmd/tendermint/main.go +++ b/cmd/tendermint/main.go @@ -21,6 +21,7 @@ Commands: gen_validator Generate new validator keypair gen_tx Generate new transaction probe_upnp Test UPnP functionality + version Show version info `) return } @@ -43,6 +44,8 @@ Commands: probe_upnp() case "unsafe_reset_priv_validator": reset_priv_validator() + case "version": + fmt.Println(config.GetString("version")) default: fmt.Printf("Unknown command %v\n", args[0]) } From 9965dd5de664fa1283361c0bac1fab02bdc91826 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Thu, 25 Jun 2015 20:28:34 -0700 Subject: [PATCH 18/24] uint* to int* whereever appropriate; https://www.reddit.com/r/golang/comments/2q5vdu/int_vs_uint/ --- account/account.go | 4 +- binary/byteslice.go | 8 ++-- binary/int.go | 3 ++ binary/reflect.go | 2 +- blockchain/pool.go | 36 +++++++++--------- blockchain/pool_test.go | 10 ++--- blockchain/reactor.go | 6 +-- blockchain/store.go | 30 +++++++-------- cmd/barak/barak.go | 8 ++-- cmd/barak/types/command.go | 2 +- cmd/barak/types/responses.go | 2 +- cmd/barak/types/validator.go | 2 +- cmd/barak/validate.go | 4 +- cmd/debora/commands.go | 4 +- cmd/tendermint/gen_tx.go | 16 +++++--- common/bit_array.go | 40 ++++++++++---------- common/bit_array_test.go | 22 +++++------ common/int.go | 16 ++++++++ common/random.go | 12 ++++++ common/word.go | 8 ++-- consensus/height_vote_set.go | 38 +++++++++---------- consensus/reactor.go | 72 ++++++++++++++++++------------------ consensus/state.go | 49 ++++++++++++------------ consensus/test.go | 2 +- consensus/types/proposal.go | 6 +-- consensus/vote_set.go | 34 ++++++++--------- consensus/vote_set_test.go | 16 ++++---- crawler/crawl.go | 4 +- merkle/iavl_node.go | 26 ++++++------- merkle/iavl_proof.go | 12 +++--- merkle/iavl_test.go | 14 +++---- merkle/iavl_tree.go | 10 ++--- merkle/simple_tree.go | 10 ++--- merkle/simple_tree_test.go | 4 +- merkle/types.go | 10 ++--- merkle/util.go | 2 +- p2p/addrbook.go | 2 +- p2p/connection.go | 18 ++++----- rpc/core/accounts.go | 2 +- rpc/core/blocks.go | 8 ++-- rpc/core/consensus.go | 6 +-- rpc/core/mempool.go | 2 +- rpc/core/names.go | 2 +- rpc/core/txs.go | 10 ++--- rpc/core/types/responses.go | 12 +++--- rpc/server/handlers.go | 2 +- rpc/test/client_ws_test.go | 16 ++++---- rpc/test/helpers.go | 10 ++--- rpc/test/tests.go | 16 ++++---- rpc/test/ws_helpers.go | 8 ++-- state/execution.go | 32 ++++++++-------- state/genesis.go | 4 +- state/priv_validator.go | 12 +++--- state/state.go | 14 +++---- state/state_test.go | 36 +++++++++--------- state/test.go | 12 +++--- state/tx_cache.go | 10 ++--- state/validator.go | 18 ++++----- state/validator_set.go | 36 +++++++++--------- state/validator_set_test.go | 6 +-- types/block.go | 18 ++++----- types/events.go | 4 +- types/names.go | 12 +++--- types/part_set.go | 24 ++++++------ types/part_set_test.go | 2 +- types/tx.go | 22 +++++------ types/tx_utils.go | 24 ++++++------ types/vote.go | 4 +- vm/gas.go | 22 +++++------ vm/native.go | 24 ++++++------ vm/stack.go | 14 +++---- vm/test/fake_app_state.go | 2 +- vm/test/vm_test.go | 16 ++++---- vm/types.go | 10 ++--- vm/vm.go | 42 ++++++++++----------- 75 files changed, 556 insertions(+), 522 deletions(-) diff --git a/account/account.go b/account/account.go index 4105588f..26f2668f 100644 --- a/account/account.go +++ b/account/account.go @@ -38,8 +38,8 @@ func HashSignBytes(chainID string, o Signable) []byte { type Account struct { Address []byte `json:"address"` PubKey PubKey `json:"pub_key"` - Sequence uint `json:"sequence"` - Balance uint64 `json:"balance"` + Sequence int `json:"sequence"` + Balance int64 `json:"balance"` Code []byte `json:"code"` // VM code StorageRoot []byte `json:"storage_root"` // VM storage merkle root. } diff --git a/binary/byteslice.go b/binary/byteslice.go index 87a42785..2e93ab93 100644 --- a/binary/byteslice.go +++ b/binary/byteslice.go @@ -10,12 +10,12 @@ const ( ) func WriteByteSlice(bz []byte, w io.Writer, n *int64, err *error) { - WriteUvarint(uint(len(bz)), w, n, err) + WriteVarint(len(bz), w, n, err) WriteTo(bz, w, n, err) } func ReadByteSlice(r io.Reader, n *int64, err *error) []byte { - length := int(ReadUvarint(r, n, err)) + length := ReadVarint(r, n, err) if *err != nil { return nil } @@ -36,7 +36,7 @@ func ReadByteSlice(r io.Reader, n *int64, err *error) []byte { //----------------------------------------------------------------------------- func WriteByteSlices(bzz [][]byte, w io.Writer, n *int64, err *error) { - WriteUvarint(uint(len(bzz)), w, n, err) + WriteVarint(len(bzz), w, n, err) for _, bz := range bzz { WriteByteSlice(bz, w, n, err) if *err != nil { @@ -46,7 +46,7 @@ func WriteByteSlices(bzz [][]byte, w io.Writer, n *int64, err *error) { } func ReadByteSlices(r io.Reader, n *int64, err *error) [][]byte { - length := int(ReadUvarint(r, n, err)) + length := ReadVarint(r, n, err) if *err != nil { return nil } diff --git a/binary/int.go b/binary/int.go index d35c82bf..3be5f224 100644 --- a/binary/int.go +++ b/binary/int.go @@ -219,6 +219,9 @@ func ReadVarint(r io.Reader, n *int64, err *error) int { return 0 } if size == 0 { + if negate { + setFirstErr(err, errors.New("Varint does not allow negative zero")) + } return 0 } buf := make([]byte, 8) diff --git a/binary/reflect.go b/binary/reflect.go index f23acb38..6eac5a46 100644 --- a/binary/reflect.go +++ b/binary/reflect.go @@ -432,7 +432,7 @@ func writeReflectBinary(rv reflect.Value, rt reflect.Type, opts Options, w io.Wr } else { // Write length length := rv.Len() - WriteUvarint(uint(length), w, n, err) + WriteVarint(length, w, n, err) // Write elems for i := 0; i < length; i++ { elemRv := rv.Index(i) diff --git a/blockchain/pool.go b/blockchain/pool.go index 5c8b280b..1d82136e 100644 --- a/blockchain/pool.go +++ b/blockchain/pool.go @@ -36,8 +36,8 @@ var ( type BlockPool struct { // block requests requestsMtx sync.Mutex - requests map[uint]*bpRequest - height uint // the lowest key in requests. + requests map[int]*bpRequest + height int // the lowest key in requests. numUnassigned int32 // number of requests not yet assigned to a peer numPending int32 // number of requests pending assignment or block response @@ -52,11 +52,11 @@ type BlockPool struct { running int32 // atomic } -func NewBlockPool(start uint, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { +func NewBlockPool(start int, requestsCh chan<- BlockRequest, timeoutsCh chan<- string) *BlockPool { return &BlockPool{ peers: make(map[string]*bpPeer), - requests: make(map[uint]*bpRequest), + requests: make(map[int]*bpRequest), height: start, numUnassigned: 0, numPending: 0, @@ -108,7 +108,7 @@ RUN_LOOP: } } -func (pool *BlockPool) GetStatus() (uint, int32) { +func (pool *BlockPool) GetStatus() (int, int32) { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() @@ -146,7 +146,7 @@ func (pool *BlockPool) PopRequest() { // Invalidates the block at pool.height. // Remove the peer and request from others. -func (pool *BlockPool) RedoRequest(height uint) { +func (pool *BlockPool) RedoRequest(height int) { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() @@ -165,7 +165,7 @@ func (pool *BlockPool) RedoRequest(height uint) { go requestRoutine(pool, height) } -func (pool *BlockPool) hasBlock(height uint) bool { +func (pool *BlockPool) hasBlock(height int) bool { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() @@ -173,7 +173,7 @@ func (pool *BlockPool) hasBlock(height uint) bool { return request != nil && request.block != nil } -func (pool *BlockPool) setPeerForRequest(height uint, peerId string) { +func (pool *BlockPool) setPeerForRequest(height int, peerId string) { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() @@ -185,7 +185,7 @@ func (pool *BlockPool) setPeerForRequest(height uint, peerId string) { request.peerId = peerId } -func (pool *BlockPool) removePeerForRequest(height uint, peerId string) { +func (pool *BlockPool) removePeerForRequest(height int, peerId string) { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() @@ -224,7 +224,7 @@ func (pool *BlockPool) getPeer(peerId string) *bpPeer { } // Sets the peer's alleged blockchain height. -func (pool *BlockPool) SetPeerHeight(peerId string, height uint) { +func (pool *BlockPool) SetPeerHeight(peerId string, height int) { pool.peersMtx.Lock() // Lock defer pool.peersMtx.Unlock() @@ -250,7 +250,7 @@ func (pool *BlockPool) RemovePeer(peerId string) { // Pick an available peer with at least the given minHeight. // If no peers are available, returns nil. -func (pool *BlockPool) pickIncrAvailablePeer(minHeight uint) *bpPeer { +func (pool *BlockPool) pickIncrAvailablePeer(minHeight int) *bpPeer { pool.peersMtx.Lock() defer pool.peersMtx.Unlock() @@ -282,7 +282,7 @@ func (pool *BlockPool) makeNextRequest() { pool.requestsMtx.Lock() // Lock defer pool.requestsMtx.Unlock() - nextHeight := pool.height + uint(len(pool.requests)) + nextHeight := pool.height + len(pool.requests) request := &bpRequest{ height: nextHeight, peerId: "", @@ -296,7 +296,7 @@ func (pool *BlockPool) makeNextRequest() { go requestRoutine(pool, nextHeight) } -func (pool *BlockPool) sendRequest(height uint, peerId string) { +func (pool *BlockPool) sendRequest(height int, peerId string) { if atomic.LoadInt32(&pool.running) == 0 { return } @@ -315,7 +315,7 @@ func (pool *BlockPool) debug() string { defer pool.requestsMtx.Unlock() str := "" - for h := pool.height; h < pool.height+uint(len(pool.requests)); h++ { + for h := pool.height; h < pool.height+len(pool.requests); h++ { if pool.requests[h] == nil { str += Fmt("H(%v):X ", h) } else { @@ -330,12 +330,12 @@ func (pool *BlockPool) debug() string { type bpPeer struct { id string - height uint + height int numRequests int32 } type bpRequest struct { - height uint + height int peerId string block *types.Block } @@ -344,7 +344,7 @@ type bpRequest struct { // Responsible for making more requests as necessary // Returns only when a block is found (e.g. AddBlock() is called) -func requestRoutine(pool *BlockPool, height uint) { +func requestRoutine(pool *BlockPool, height int) { for { var peer *bpPeer = nil PICK_LOOP: @@ -393,6 +393,6 @@ func requestRoutine(pool *BlockPool, height uint) { //------------------------------------- type BlockRequest struct { - Height uint + Height int PeerId string } diff --git a/blockchain/pool_test.go b/blockchain/pool_test.go index c07a11d8..e6ff3be4 100644 --- a/blockchain/pool_test.go +++ b/blockchain/pool_test.go @@ -11,14 +11,14 @@ import ( type testPeer struct { id string - height uint + height int } -func makePeers(numPeers int, minHeight, maxHeight uint) map[string]testPeer { +func makePeers(numPeers int, minHeight, maxHeight int) map[string]testPeer { peers := make(map[string]testPeer, numPeers) for i := 0; i < numPeers; i++ { peerId := RandStr(12) - height := minHeight + uint(rand.Intn(int(maxHeight-minHeight))) + height := minHeight + rand.Intn(maxHeight-minHeight) peers[peerId] = testPeer{peerId, height} } return peers @@ -26,7 +26,7 @@ func makePeers(numPeers int, minHeight, maxHeight uint) map[string]testPeer { func TestBasic(t *testing.T) { peers := makePeers(10, 0, 1000) - start := uint(42) + start := 42 timeoutsCh := make(chan string, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) @@ -78,7 +78,7 @@ func TestBasic(t *testing.T) { func TestTimeout(t *testing.T) { peers := makePeers(10, 0, 1000) - start := uint(42) + start := 42 timeoutsCh := make(chan string, 100) requestsCh := make(chan BlockRequest, 100) pool := NewBlockPool(start, requestsCh, timeoutsCh) diff --git a/blockchain/reactor.go b/blockchain/reactor.go index 01d0865d..fa7ff693 100644 --- a/blockchain/reactor.go +++ b/blockchain/reactor.go @@ -301,7 +301,7 @@ func DecodeMessage(bz []byte) (msgType byte, msg BlockchainMessage, err error) { //------------------------------------- type bcBlockRequestMessage struct { - Height uint + Height int } func (m *bcBlockRequestMessage) String() string { @@ -321,7 +321,7 @@ func (m *bcBlockResponseMessage) String() string { //------------------------------------- type bcStatusRequestMessage struct { - Height uint + Height int } func (m *bcStatusRequestMessage) String() string { @@ -331,7 +331,7 @@ func (m *bcStatusRequestMessage) String() string { //------------------------------------- type bcStatusResponseMessage struct { - Height uint + Height int } func (m *bcStatusResponseMessage) String() string { diff --git a/blockchain/store.go b/blockchain/store.go index d0618a23..fa7e696e 100644 --- a/blockchain/store.go +++ b/blockchain/store.go @@ -25,7 +25,7 @@ well as the Validation. In the future this may change, perhaps by moving the Validation data outside the Block. */ type BlockStore struct { - height uint + height int db dbm.DB } @@ -38,7 +38,7 @@ func NewBlockStore(db dbm.DB) *BlockStore { } // Height() returns the last known contiguous block height. -func (bs *BlockStore) Height() uint { +func (bs *BlockStore) Height() int { return bs.height } @@ -50,7 +50,7 @@ func (bs *BlockStore) GetReader(key []byte) io.Reader { return bytes.NewReader(bytez) } -func (bs *BlockStore) LoadBlock(height uint) *types.Block { +func (bs *BlockStore) LoadBlock(height int) *types.Block { var n int64 var err error r := bs.GetReader(calcBlockMetaKey(height)) @@ -62,7 +62,7 @@ func (bs *BlockStore) LoadBlock(height uint) *types.Block { panic(Fmt("Error reading block meta: %v", err)) } bytez := []byte{} - for i := uint(0); i < meta.PartsHeader.Total; i++ { + for i := 0; i < meta.PartsHeader.Total; i++ { part := bs.LoadBlockPart(height, i) bytez = append(bytez, part.Bytes...) } @@ -73,7 +73,7 @@ func (bs *BlockStore) LoadBlock(height uint) *types.Block { return block } -func (bs *BlockStore) LoadBlockPart(height uint, index uint) *types.Part { +func (bs *BlockStore) LoadBlockPart(height int, index int) *types.Part { var n int64 var err error r := bs.GetReader(calcBlockPartKey(height, index)) @@ -87,7 +87,7 @@ func (bs *BlockStore) LoadBlockPart(height uint, index uint) *types.Part { return part } -func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta { +func (bs *BlockStore) LoadBlockMeta(height int) *types.BlockMeta { var n int64 var err error r := bs.GetReader(calcBlockMetaKey(height)) @@ -103,7 +103,7 @@ func (bs *BlockStore) LoadBlockMeta(height uint) *types.BlockMeta { // The +2/3 and other Precommit-votes for block at `height`. // This Validation comes from block.LastValidation for `height+1`. -func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation { +func (bs *BlockStore) LoadBlockValidation(height int) *types.Validation { var n int64 var err error r := bs.GetReader(calcBlockValidationKey(height)) @@ -118,7 +118,7 @@ func (bs *BlockStore) LoadBlockValidation(height uint) *types.Validation { } // NOTE: the Precommit-vote heights are for the block at `height` -func (bs *BlockStore) LoadSeenValidation(height uint) *types.Validation { +func (bs *BlockStore) LoadSeenValidation(height int) *types.Validation { var n int64 var err error r := bs.GetReader(calcSeenValidationKey(height)) @@ -152,7 +152,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s bs.db.Set(calcBlockMetaKey(height), metaBytes) // Save block parts - for i := uint(0); i < blockParts.Total(); i++ { + for i := 0; i < blockParts.Total(); i++ { bs.saveBlockPart(height, i, blockParts.GetPart(i)) } @@ -171,7 +171,7 @@ func (bs *BlockStore) SaveBlock(block *types.Block, blockParts *types.PartSet, s bs.height = height } -func (bs *BlockStore) saveBlockPart(height uint, index uint, part *types.Part) { +func (bs *BlockStore) saveBlockPart(height int, index int, part *types.Part) { if height != bs.height+1 { panic(Fmt("BlockStore can only save contiguous blocks. Wanted %v, got %v", bs.height+1, height)) } @@ -181,19 +181,19 @@ func (bs *BlockStore) saveBlockPart(height uint, index uint, part *types.Part) { //----------------------------------------------------------------------------- -func calcBlockMetaKey(height uint) []byte { +func calcBlockMetaKey(height int) []byte { return []byte(fmt.Sprintf("H:%v", height)) } -func calcBlockPartKey(height uint, partIndex uint) []byte { +func calcBlockPartKey(height int, partIndex int) []byte { return []byte(fmt.Sprintf("P:%v:%v", height, partIndex)) } -func calcBlockValidationKey(height uint) []byte { +func calcBlockValidationKey(height int) []byte { return []byte(fmt.Sprintf("V:%v", height)) } -func calcSeenValidationKey(height uint) []byte { +func calcSeenValidationKey(height int) []byte { return []byte(fmt.Sprintf("SV:%v", height)) } @@ -202,7 +202,7 @@ func calcSeenValidationKey(height uint) []byte { var blockStoreKey = []byte("blockStore") type BlockStoreStateJSON struct { - Height uint + Height int } func (bsj BlockStoreStateJSON) Save(db dbm.DB) { diff --git a/cmd/barak/barak.go b/cmd/barak/barak.go index de0a9c62..affe1693 100644 --- a/cmd/barak/barak.go +++ b/cmd/barak/barak.go @@ -20,7 +20,7 @@ import ( type BarakOptions struct { Validators []Validator ListenAddress string - StartNonce uint64 + StartNonce int64 Registries []string } @@ -74,7 +74,7 @@ func NewBarakFromOptions(opt *BarakOptions) *Barak { type Barak struct { mtx sync.Mutex pid int - nonce uint64 + nonce int64 processes map[string]*pcm.Process validators []Validator listeners []net.Listener @@ -82,7 +82,7 @@ type Barak struct { registries []string } -func NewBarak(rootDir string, nonce uint64, validators []Validator) *Barak { +func NewBarak(rootDir string, nonce int64, validators []Validator) *Barak { return &Barak{ pid: os.Getpid(), nonce: nonce, @@ -243,7 +243,7 @@ func (brk *Barak) WritePidFile() { } } -func (brk *Barak) CheckIncrNonce(newNonce uint64) error { +func (brk *Barak) CheckIncrNonce(newNonce int64) error { brk.mtx.Lock() defer brk.mtx.Unlock() if brk.nonce+1 != newNonce { diff --git a/cmd/barak/types/command.go b/cmd/barak/types/command.go index 92df4691..074502e0 100644 --- a/cmd/barak/types/command.go +++ b/cmd/barak/types/command.go @@ -11,7 +11,7 @@ type AuthCommand struct { } type NoncedCommand struct { - Nonce uint64 + Nonce int64 Command } diff --git a/cmd/barak/types/responses.go b/cmd/barak/types/responses.go index 85fc77bc..f8b138d8 100644 --- a/cmd/barak/types/responses.go +++ b/cmd/barak/types/responses.go @@ -6,7 +6,7 @@ import ( type ResponseStatus struct { Pid int - Nonce uint64 + Nonce int64 Validators []Validator } diff --git a/cmd/barak/types/validator.go b/cmd/barak/types/validator.go index 252f3dc5..2db48609 100644 --- a/cmd/barak/types/validator.go +++ b/cmd/barak/types/validator.go @@ -5,6 +5,6 @@ import ( ) type Validator struct { - VotingPower uint64 + VotingPower int64 PubKey acm.PubKey } diff --git a/cmd/barak/validate.go b/cmd/barak/validate.go index df7b420b..9653f732 100644 --- a/cmd/barak/validate.go +++ b/cmd/barak/validate.go @@ -6,8 +6,8 @@ import ( ) func validate(signBytes []byte, validators []Validator, signatures []acm.Signature) bool { - var signedPower uint64 - var totalPower uint64 + var signedPower int64 + var totalPower int64 for i, val := range validators { if val.PubKey.VerifyBytes(signBytes, signatures[i]) { signedPower += val.VotingPower diff --git a/cmd/debora/commands.go b/cmd/debora/commands.go index 316e48ee..dfca0737 100644 --- a/cmd/debora/commands.go +++ b/cmd/debora/commands.go @@ -104,7 +104,7 @@ func DownloadFile(privKey acm.PrivKey, remote string, command btypes.CommandServ // Utility method to get nonce from the remote. // The next command should include the returned nonce+1 as nonce. -func GetNonce(remote string) (uint64, error) { +func GetNonce(remote string) (int64, error) { response, err := GetStatus(remote) return response.Nonce, err } @@ -118,7 +118,7 @@ func GetStatus(remote string) (response btypes.ResponseStatus, err error) { } // Each developer runs this -func SignCommand(privKey acm.PrivKey, nonce uint64, command btypes.Command) ([]byte, acm.Signature) { +func SignCommand(privKey acm.PrivKey, nonce int64, command btypes.Command) ([]byte, acm.Signature) { noncedCommand := btypes.NoncedCommand{ Nonce: nonce, Command: command, diff --git a/cmd/tendermint/gen_tx.go b/cmd/tendermint/gen_tx.go index d499fad8..4a04e272 100644 --- a/cmd/tendermint/gen_tx.go +++ b/cmd/tendermint/gen_tx.go @@ -32,13 +32,17 @@ func getByteSliceFromHex(prompt string) []byte { return bytes } -func getUint64(prompt string) uint64 { +func getInt(prompt string) int { input := getString(prompt) i, err := strconv.Atoi(input) if err != nil { - Exit(Fmt("Not a valid uint64 amount: %v\nError: %v\n", input, err)) + Exit(Fmt("Not a valid int64 amount: %v\nError: %v\n", input, err)) } - return uint64(i) + return i +} + +func getInt64(prompt string) int64 { + return int64(getInt(prompt)) } func gen_tx() { @@ -68,16 +72,16 @@ func gen_tx() { } // Get the amount to send from src account - srcSendAmount := getUint64(Fmt("Enter amount to send from %X (total: %v): ", srcAccountAddress, srcAccountBalanceStr)) + srcSendAmount := getInt64(Fmt("Enter amount to send from %X (total: %v): ", srcAccountAddress, srcAccountBalanceStr)) // Get the next sequence of src account - srcSendSequence := uint(getUint64(Fmt("Enter next sequence for %X (guess: %v): ", srcAccountAddress, srcAccountSequenceStr))) + srcSendSequence := getInt(Fmt("Enter next sequence for %X (guess: %v): ", srcAccountAddress, srcAccountSequenceStr)) // Get dest address dstAddress := getByteSliceFromHex("Enter destination address: ") // Get the amount to send to dst account - dstSendAmount := getUint64(Fmt("Enter amount to send to %X: ", dstAddress)) + dstSendAmount := getInt64(Fmt("Enter amount to send to %X: ", dstAddress)) // Construct SendTx tx := &types.SendTx{ diff --git a/common/bit_array.go b/common/bit_array.go index 034beb0f..d478d02e 100644 --- a/common/bit_array.go +++ b/common/bit_array.go @@ -9,12 +9,12 @@ import ( type BitArray struct { mtx sync.Mutex - Bits uint `json:"bits"` // NOTE: persisted via reflect, must be exported + Bits int `json:"bits"` // NOTE: persisted via reflect, must be exported Elems []uint64 `json:"elems"` // NOTE: persisted via reflect, must be exported } // There is no BitArray whose Size is 0. Use nil instead. -func NewBitArray(bits uint) *BitArray { +func NewBitArray(bits int) *BitArray { if bits == 0 { return nil } @@ -24,7 +24,7 @@ func NewBitArray(bits uint) *BitArray { } } -func (bA *BitArray) Size() uint { +func (bA *BitArray) Size() int { if bA == nil { return 0 } @@ -32,7 +32,7 @@ func (bA *BitArray) Size() uint { } // NOTE: behavior is undefined if i >= bA.Bits -func (bA *BitArray) GetIndex(i uint) bool { +func (bA *BitArray) GetIndex(i int) bool { if bA == nil { return false } @@ -41,15 +41,15 @@ func (bA *BitArray) GetIndex(i uint) bool { return bA.getIndex(i) } -func (bA *BitArray) getIndex(i uint) bool { +func (bA *BitArray) getIndex(i int) bool { if i >= bA.Bits { return false } - return bA.Elems[i/64]&(uint64(1)<<(i%64)) > 0 + return bA.Elems[i/64]&(uint64(1)< 0 } // NOTE: behavior is undefined if i >= bA.Bits -func (bA *BitArray) SetIndex(i uint, v bool) bool { +func (bA *BitArray) SetIndex(i int, v bool) bool { if bA == nil { return false } @@ -58,14 +58,14 @@ func (bA *BitArray) SetIndex(i uint, v bool) bool { return bA.setIndex(i, v) } -func (bA *BitArray) setIndex(i uint, v bool) bool { +func (bA *BitArray) setIndex(i int, v bool) bool { if i >= bA.Bits { return false } if v { - bA.Elems[i/64] |= (uint64(1) << (i % 64)) + bA.Elems[i/64] |= (uint64(1) << uint(i%64)) } else { - bA.Elems[i/64] &= ^(uint64(1) << (i % 64)) + bA.Elems[i/64] &= ^(uint64(1) << uint(i%64)) } return true } @@ -88,7 +88,7 @@ func (bA *BitArray) copy() *BitArray { } } -func (bA *BitArray) copyBits(bits uint) *BitArray { +func (bA *BitArray) copyBits(bits int) *BitArray { c := make([]uint64, (bits+63)/64) copy(c, bA.Elems) return &BitArray{ @@ -104,7 +104,7 @@ func (bA *BitArray) Or(o *BitArray) *BitArray { } bA.mtx.Lock() defer bA.mtx.Unlock() - c := bA.copyBits(MaxUint(bA.Bits, o.Bits)) + c := bA.copyBits(MaxInt(bA.Bits, o.Bits)) for i := 0; i < len(c.Elems); i++ { c.Elems[i] |= o.Elems[i] } @@ -122,7 +122,7 @@ func (bA *BitArray) And(o *BitArray) *BitArray { } func (bA *BitArray) and(o *BitArray) *BitArray { - c := bA.copyBits(MinUint(bA.Bits, o.Bits)) + c := bA.copyBits(MinInt(bA.Bits, o.Bits)) for i := 0; i < len(c.Elems); i++ { c.Elems[i] &= o.Elems[i] } @@ -153,7 +153,7 @@ func (bA *BitArray) Sub(o *BitArray) *BitArray { for i := 0; i < len(o.Elems)-1; i++ { c.Elems[i] &= ^c.Elems[i] } - i := uint(len(o.Elems) - 1) + i := len(o.Elems) - 1 if i >= 0 { for idx := i * 64; idx < o.Bits; idx++ { c.setIndex(idx, c.getIndex(idx) && !o.GetIndex(idx)) @@ -182,10 +182,10 @@ func (bA *BitArray) IsFull() bool { // Check that the last element has (lastElemBits) 1's lastElemBits := (bA.Bits+63)%64 + 1 lastElem := bA.Elems[len(bA.Elems)-1] - return (lastElem+1)&((uint64(1)< 0 { - return 64*uint(elemIdx) + uint(bitIdx), true + return 64*elemIdx + bitIdx, true } } panic("should not happen") } } else { // Special case for last elem, to ignore straggler bits - elemBits := int(bA.Bits) % 64 + elemBits := bA.Bits % 64 if elemBits == 0 { elemBits = 64 } @@ -220,7 +220,7 @@ func (bA *BitArray) PickRandom() (uint, bool) { for j := 0; j < elemBits; j++ { bitIdx := ((j + randBitStart) % elemBits) if (bA.Elems[elemIdx] & (uint64(1) << uint(bitIdx))) > 0 { - return 64*uint(elemIdx) + uint(bitIdx), true + return 64*elemIdx + bitIdx, true } } } @@ -250,7 +250,7 @@ func (bA *BitArray) stringIndented(indent string) string { lines := []string{} bits := "" - for i := uint(0); i < bA.Bits; i++ { + for i := 0; i < bA.Bits; i++ { if bA.getIndex(i) { bits += "X" } else { diff --git a/common/bit_array_test.go b/common/bit_array_test.go index a0a1f397..93274aab 100644 --- a/common/bit_array_test.go +++ b/common/bit_array_test.go @@ -4,15 +4,15 @@ import ( "testing" ) -func randBitArray(bits uint) (*BitArray, []byte) { - src := RandBytes(int((bits + 7) / 8)) +func randBitArray(bits int) (*BitArray, []byte) { + src := RandBytes((bits + 7) / 8) bA := NewBitArray(bits) - for i := uint(0); i < uint(len(src)); i++ { - for j := uint(0); j < 8; j++ { + for i := 0; i < len(src); i++ { + for j := 0; j < 8; j++ { if i*8+j >= bits { return bA, src } - setBit := src[i]&(1< 0 + setBit := src[i]&(1< 0 bA.SetIndex(i*8+j, setBit) } } @@ -31,7 +31,7 @@ func TestAnd(t *testing.T) { if len(bA3.Elems) != len(bA2.Elems) { t.Error("Expected min elems length") } - for i := uint(0); i < bA3.Bits; i++ { + for i := 0; i < bA3.Bits; i++ { expected := bA1.GetIndex(i) && bA2.GetIndex(i) if bA3.GetIndex(i) != expected { t.Error("Wrong bit from bA3", i, bA1.GetIndex(i), bA2.GetIndex(i), bA3.GetIndex(i)) @@ -51,7 +51,7 @@ func TestOr(t *testing.T) { if len(bA3.Elems) != len(bA1.Elems) { t.Error("Expected max elems length") } - for i := uint(0); i < bA3.Bits; i++ { + for i := 0; i < bA3.Bits; i++ { expected := bA1.GetIndex(i) || bA2.GetIndex(i) if bA3.GetIndex(i) != expected { t.Error("Wrong bit from bA3", i, bA1.GetIndex(i), bA2.GetIndex(i), bA3.GetIndex(i)) @@ -71,7 +71,7 @@ func TestSub1(t *testing.T) { if len(bA3.Elems) != len(bA1.Elems) { t.Error("Expected bA1 elems length") } - for i := uint(0); i < bA3.Bits; i++ { + for i := 0; i < bA3.Bits; i++ { expected := bA1.GetIndex(i) if bA2.GetIndex(i) { expected = false @@ -94,7 +94,7 @@ func TestSub2(t *testing.T) { if len(bA3.Elems) != len(bA1.Elems) { t.Error("Expected bA1 elems length") } - for i := uint(0); i < bA3.Bits; i++ { + for i := 0; i < bA3.Bits; i++ { expected := bA1.GetIndex(i) if i < bA2.Bits && bA2.GetIndex(i) { expected = false @@ -108,12 +108,12 @@ func TestSub2(t *testing.T) { func TestPickRandom(t *testing.T) { for idx := 0; idx < 123; idx++ { bA1 := NewBitArray(123) - bA1.SetIndex(uint(idx), true) + bA1.SetIndex(idx, true) index, ok := bA1.PickRandom() if !ok { t.Fatal("Expected to pick element but got none") } - if index != uint(idx) { + if index != idx { t.Fatalf("Expected to pick element at %v but got wrong index", idx) } } diff --git a/common/int.go b/common/int.go index c6e85dc6..50e86a07 100644 --- a/common/int.go +++ b/common/int.go @@ -37,3 +37,19 @@ func PutUint64BE(dest []byte, i uint64) { func GetUint64BE(src []byte) uint64 { return binary.BigEndian.Uint64(src) } + +func PutInt64LE(dest []byte, i int64) { + binary.LittleEndian.PutUint64(dest, uint64(i)) +} + +func GetInt64LE(src []byte) int64 { + return int64(binary.LittleEndian.Uint64(src)) +} + +func PutInt64BE(dest []byte, i int64) { + binary.BigEndian.PutUint64(dest, uint64(i)) +} + +func GetInt64BE(src []byte) int64 { + return int64(binary.BigEndian.Uint64(src)) +} diff --git a/common/random.go b/common/random.go index 62a50922..e1d6046d 100644 --- a/common/random.go +++ b/common/random.go @@ -62,6 +62,18 @@ func RandUint() uint { return uint(rand.Int()) } +func RandInt16() int16 { + return int16(rand.Uint32() & (1<<16 - 1)) +} + +func RandInt32() int32 { + return int32(rand.Uint32()) +} + +func RandInt64() int64 { + return int64(rand.Uint32())<<32 + int64(rand.Uint32()) +} + func RandInt() int { return rand.Int() } diff --git a/common/word.go b/common/word.go index 264a7dcc..893b136e 100644 --- a/common/word.go +++ b/common/word.go @@ -28,9 +28,9 @@ func (w Word256) Compare(other Word256) int { return bytes.Compare(w[:], other[:]) } -func Uint64ToWord256(i uint64) Word256 { +func Int64ToWord256(i int64) Word256 { buf := [8]byte{} - PutUint64BE(buf[:], i) + PutInt64BE(buf[:], i) return LeftPadWord256(buf[:]) } @@ -44,9 +44,9 @@ func LeftPadWord256(bz []byte) (word Word256) { return } -func Uint64FromWord256(word Word256) uint64 { +func Int64FromWord256(word Word256) int64 { buf := word.Postfix(8) - return GetUint64BE(buf) + return GetInt64BE(buf) } //------------------------------------- diff --git a/consensus/height_vote_set.go b/consensus/height_vote_set.go index 1377c67d..0dce526d 100644 --- a/consensus/height_vote_set.go +++ b/consensus/height_vote_set.go @@ -27,39 +27,39 @@ we create a new entry in roundVoteSets but also remember the peer to prevent abuse. */ type HeightVoteSet struct { - height uint + height int valSet *sm.ValidatorSet mtx sync.Mutex - round uint // max tracked round - roundVoteSets map[uint]RoundVoteSet // keys: [0...round] - peerCatchupRounds map[string]uint // keys: peer.Key; values: round + round int // max tracked round + roundVoteSets map[int]RoundVoteSet // keys: [0...round] + peerCatchupRounds map[string]int // keys: peer.Key; values: round } -func NewHeightVoteSet(height uint, valSet *sm.ValidatorSet) *HeightVoteSet { +func NewHeightVoteSet(height int, valSet *sm.ValidatorSet) *HeightVoteSet { hvs := &HeightVoteSet{ height: height, valSet: valSet, - roundVoteSets: make(map[uint]RoundVoteSet), - peerCatchupRounds: make(map[string]uint), + roundVoteSets: make(map[int]RoundVoteSet), + peerCatchupRounds: make(map[string]int), } hvs.addRound(0) hvs.round = 0 return hvs } -func (hvs *HeightVoteSet) Height() uint { +func (hvs *HeightVoteSet) Height() int { return hvs.height } -func (hvs *HeightVoteSet) Round() uint { +func (hvs *HeightVoteSet) Round() int { hvs.mtx.Lock() defer hvs.mtx.Unlock() return hvs.round } // Create more RoundVoteSets up to round. -func (hvs *HeightVoteSet) SetRound(round uint) { +func (hvs *HeightVoteSet) SetRound(round int) { hvs.mtx.Lock() defer hvs.mtx.Unlock() if hvs.round != 0 && (round < hvs.round+1) { @@ -74,7 +74,7 @@ func (hvs *HeightVoteSet) SetRound(round uint) { hvs.round = round } -func (hvs *HeightVoteSet) addRound(round uint) { +func (hvs *HeightVoteSet) addRound(round int) { if _, ok := hvs.roundVoteSets[round]; ok { panic("addRound() for an existing round") } @@ -88,7 +88,7 @@ func (hvs *HeightVoteSet) addRound(round uint) { // Duplicate votes return added=false, err=nil. // By convention, peerKey is "" if origin is self. -func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { +func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey string) (added bool, index int, err error) { hvs.mtx.Lock() defer hvs.mtx.Unlock() voteSet := hvs.getVoteSet(vote.Round, vote.Type) @@ -108,13 +108,13 @@ func (hvs *HeightVoteSet) AddByAddress(address []byte, vote *types.Vote, peerKey return } -func (hvs *HeightVoteSet) Prevotes(round uint) *VoteSet { +func (hvs *HeightVoteSet) Prevotes(round int) *VoteSet { hvs.mtx.Lock() defer hvs.mtx.Unlock() return hvs.getVoteSet(round, types.VoteTypePrevote) } -func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet { +func (hvs *HeightVoteSet) Precommits(round int) *VoteSet { hvs.mtx.Lock() defer hvs.mtx.Unlock() return hvs.getVoteSet(round, types.VoteTypePrecommit) @@ -125,15 +125,15 @@ func (hvs *HeightVoteSet) Precommits(round uint) *VoteSet { func (hvs *HeightVoteSet) POLRound() int { hvs.mtx.Lock() defer hvs.mtx.Unlock() - for r := int(hvs.round); r >= 0; r-- { - if hvs.getVoteSet(uint(r), types.VoteTypePrevote).HasTwoThirdsMajority() { - return int(r) + for r := hvs.round; r >= 0; r-- { + if hvs.getVoteSet(r, types.VoteTypePrevote).HasTwoThirdsMajority() { + return r } } return -1 } -func (hvs *HeightVoteSet) getVoteSet(round uint, type_ byte) *VoteSet { +func (hvs *HeightVoteSet) getVoteSet(round int, type_ byte) *VoteSet { rvs, ok := hvs.roundVoteSets[round] if !ok { return nil @@ -155,7 +155,7 @@ func (hvs *HeightVoteSet) String() string { func (hvs *HeightVoteSet) StringIndented(indent string) string { vsStrings := make([]string, 0, (len(hvs.roundVoteSets)+1)*2) // rounds 0 ~ hvs.round inclusive - for round := uint(0); round <= hvs.round; round++ { + for round := 0; round <= hvs.round; round++ { voteSetString := hvs.roundVoteSets[round].Prevotes.StringShort() vsStrings = append(vsStrings, voteSetString) voteSetString = hvs.roundVoteSets[round].Precommits.StringShort() diff --git a/consensus/reactor.go b/consensus/reactor.go index 22c4182c..56a873d3 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -227,7 +227,7 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte } // Broadcasts HasVoteMessage to peers that care. -func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote, index uint) { +func (conR *ConsensusReactor) broadcastHasVoteMessage(vote *types.Vote, index int) { msg := &HasVoteMessage{ Height: vote.Height, Round: vote.Round, @@ -278,7 +278,7 @@ func makeRoundStepMessages(rs *RoundState) (nrsMsg *NewRoundStepMessage, csMsg * Height: rs.Height, Round: rs.Round, Step: rs.Step, - SecondsSinceStartTime: uint(time.Now().Sub(rs.StartTime).Seconds()), + SecondsSinceStartTime: int(time.Now().Sub(rs.StartTime).Seconds()), LastCommitRound: rs.LastCommit.Round(), } if rs.Step == RoundStepCommit { @@ -415,8 +415,8 @@ OUTER_LOOP: if 0 <= rs.Proposal.POLRound { msg := &ProposalPOLMessage{ Height: rs.Height, - ProposalPOLRound: uint(rs.Proposal.POLRound), - ProposalPOL: rs.Votes.Prevotes(uint(rs.Proposal.POLRound)).BitArray(), + ProposalPOLRound: rs.Proposal.POLRound, + ProposalPOL: rs.Votes.Prevotes(rs.Proposal.POLRound).BitArray(), } peer.Send(DataChannel, msg) } @@ -482,7 +482,7 @@ OUTER_LOOP: if validation == nil { return false } else if *prsVoteSet == nil { - ps.EnsureVoteBitArrays(validation.Height(), uint(len(validation.Precommits)), prs) + ps.EnsureVoteBitArrays(validation.Height(), len(validation.Precommits), prs) // We could return true here (useful work was done) // or, we can continue since prsVoteSet is no longer nil. if *prsVoteSet == nil { @@ -522,7 +522,7 @@ OUTER_LOOP: } // If there are POLPrevotes to send... if 0 <= prs.ProposalPOLRound { - if polPrevotes := rs.Votes.Prevotes(uint(prs.ProposalPOLRound)); polPrevotes != nil { + if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { if trySendVote(polPrevotes, &prs.ProposalPOL) { continue OUTER_LOOP } @@ -580,8 +580,8 @@ OUTER_LOOP: // Read only when returned by PeerState.GetRoundState(). type PeerRoundState struct { - Height uint // Height peer is at - Round uint // Round peer is at + Height int // Height peer is at + Round int // Round peer is at Step RoundStepType // Step peer is at StartTime time.Time // Estimated start of round 0 at this height Proposal bool // True if peer has proposal for this round @@ -591,7 +591,7 @@ type PeerRoundState struct { ProposalPOL *BitArray // nil until ProposalPOLMessage received. Prevotes *BitArray // All votes peer has for this round Precommits *BitArray // All precommits peer has for this round - LastCommitRound uint // Round of commit for last height. + LastCommitRound int // Round of commit for last height. LastCommit *BitArray // All commit precommits of commit for last height. CatchupCommitRound int // Round that we believe commit round is. CatchupCommit *BitArray // All commit precommits peer has for this height @@ -638,12 +638,12 @@ func (ps *PeerState) SetHasProposal(proposal *Proposal) { ps.Proposal = true ps.ProposalBlockPartsHeader = proposal.BlockPartsHeader - ps.ProposalBlockParts = NewBitArray(uint(proposal.BlockPartsHeader.Total)) + ps.ProposalBlockParts = NewBitArray(proposal.BlockPartsHeader.Total) ps.ProposalPOLRound = proposal.POLRound ps.ProposalPOL = nil // Nil until ProposalPOLMessage received. } -func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint) { +func (ps *PeerState) SetHasProposalBlockPart(height int, round int, index int) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -651,13 +651,13 @@ func (ps *PeerState) SetHasProposalBlockPart(height uint, round uint, index uint return } - ps.ProposalBlockParts.SetIndex(uint(index), true) + ps.ProposalBlockParts.SetIndex(index, true) } // prs: If given, will also update this PeerRoundState copy. // NOTE: It's important to make sure that numValidators actually matches // what the node sees as the number of validators for height. -func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *PeerRoundState) { +func (ps *PeerState) EnsureVoteBitArrays(height int, numValidators int, prs *PeerRoundState) { ps.mtx.Lock() defer ps.mtx.Unlock() @@ -690,14 +690,14 @@ func (ps *PeerState) EnsureVoteBitArrays(height uint, numValidators uint, prs *P } } -func (ps *PeerState) SetHasVote(vote *types.Vote, index uint) { +func (ps *PeerState) SetHasVote(vote *types.Vote, index int) { ps.mtx.Lock() defer ps.mtx.Unlock() ps.setHasVote(vote.Height, vote.Round, vote.Type, index) } -func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) { +func (ps *PeerState) setHasVote(height int, round int, type_ byte, index int) { if ps.Height == height+1 && ps.LastCommitRound == round && type_ == types.VoteTypePrecommit { // Special case for LastCommit. ps.LastCommit.SetIndex(index, true) @@ -711,13 +711,13 @@ func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) // By here, ps.Height is height. switch type_ { case types.VoteTypePrevote: - if ps.ProposalPOLRound == int(round) { + if ps.ProposalPOLRound == round { ps.ProposalPOL.SetIndex(index, true) } ps.Prevotes.SetIndex(index, true) log.Debug("SetHasVote", "peer", ps.Key, "prevotes", ps.Prevotes, "index", index) case types.VoteTypePrecommit: - if ps.CatchupCommitRound == int(round) { + if ps.CatchupCommitRound == round { ps.CatchupCommit.SetIndex(index, true) } ps.Precommits.SetIndex(index, true) @@ -728,20 +728,20 @@ func (ps *PeerState) setHasVote(height uint, round uint, type_ byte, index uint) } // NOTE: 'round' is what we know to be the commit round for height. -func (ps *PeerState) EnsureCatchupCommitRound(height, round uint) { +func (ps *PeerState) EnsureCatchupCommitRound(height, round int) { ps.mtx.Lock() defer ps.mtx.Unlock() if ps.Height != height { return } - if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != int(round) { + if ps.CatchupCommitRound != -1 && ps.CatchupCommitRound != round { panic(Fmt("Conflicting CatchupCommitRound. Height: %v, Orig: %v, New: %v", height, ps.CatchupCommitRound, round)) } - if ps.CatchupCommitRound == int(round) { + if ps.CatchupCommitRound == round { return // Nothing to do! } - ps.CatchupCommitRound = int(round) + ps.CatchupCommitRound = round ps.CatchupCommit = nil } @@ -771,7 +771,7 @@ func (ps *PeerState) ApplyNewRoundStepMessage(msg *NewRoundStepMessage, rs *Roun ps.Prevotes = nil ps.Precommits = nil } - if psHeight == msg.Height && psRound != msg.Round && int(msg.Round) == psCatchupCommitRound { + if psHeight == msg.Height && psRound != msg.Round && msg.Round == psCatchupCommitRound { // Peer caught up to CatchupCommitRound. // Preserve psCatchupCommit! // NOTE: We prefer to use prs.Precommits if @@ -823,7 +823,7 @@ func (ps *PeerState) ApplyProposalPOLMessage(msg *ProposalPOLMessage) { if ps.Height != msg.Height { return } - if ps.ProposalPOLRound != int(msg.ProposalPOLRound) { + if ps.ProposalPOLRound != msg.ProposalPOLRound { return } @@ -871,11 +871,11 @@ func DecodeMessage(bz []byte) (msgType byte, msg ConsensusMessage, err error) { // For every height/round/step transition type NewRoundStepMessage struct { - Height uint - Round uint + Height int + Round int Step RoundStepType - SecondsSinceStartTime uint - LastCommitRound uint + SecondsSinceStartTime int + LastCommitRound int } func (m *NewRoundStepMessage) String() string { @@ -886,7 +886,7 @@ func (m *NewRoundStepMessage) String() string { //------------------------------------- type CommitStepMessage struct { - Height uint + Height int BlockPartsHeader types.PartSetHeader BlockParts *BitArray } @@ -908,8 +908,8 @@ func (m *ProposalMessage) String() string { //------------------------------------- type ProposalPOLMessage struct { - Height uint - ProposalPOLRound uint + Height int + ProposalPOLRound int ProposalPOL *BitArray } @@ -920,8 +920,8 @@ func (m *ProposalPOLMessage) String() string { //------------------------------------- type BlockPartMessage struct { - Height uint - Round uint + Height int + Round int Part *types.Part } @@ -932,7 +932,7 @@ func (m *BlockPartMessage) String() string { //------------------------------------- type VoteMessage struct { - ValidatorIndex uint + ValidatorIndex int Vote *types.Vote } @@ -943,10 +943,10 @@ func (m *VoteMessage) String() string { //------------------------------------- type HasVoteMessage struct { - Height uint - Round uint + Height int + Round int Type byte - Index uint + Index int } func (m *HasVoteMessage) String() string { diff --git a/consensus/state.go b/consensus/state.go index 1811c117..b74e9a0d 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -201,8 +201,8 @@ func (rs RoundStepType) String() string { // Immutable when returned from ConsensusState.GetRoundState() type RoundState struct { - Height uint // Height we are working on - Round uint + Height int // Height we are working on + Round int Step RoundStepType StartTime time.Time CommitTime time.Time // Subjective time when +2/3 precommits for Block at Round were found @@ -210,7 +210,7 @@ type RoundState struct { Proposal *Proposal ProposalBlock *types.Block ProposalBlockParts *types.PartSet - LockedRound uint + LockedRound int LockedBlock *types.Block LockedBlockParts *types.PartSet Votes *HeightVoteSet @@ -299,7 +299,7 @@ func (cs *ConsensusState) reconstructLastCommit(state *sm.State) { lastPrecommits := NewVoteSet(state.LastBlockHeight, 0, types.VoteTypePrecommit, state.LastBondedValidators) seenValidation := cs.blockStore.LoadSeenValidation(state.LastBlockHeight) for idx, precommit := range seenValidation.Precommits { - added, _, err := lastPrecommits.AddByIndex(uint(idx), precommit) + added, _, err := lastPrecommits.AddByIndex(idx, precommit) if !added || err != nil { panic(Fmt("Failed to reconstruct LastCommit: %v", err)) } @@ -339,7 +339,7 @@ func (cs *ConsensusState) Start() { } // EnterNewRound(height, 0) at cs.StartTime. -func (cs *ConsensusState) scheduleRound0(height uint) { +func (cs *ConsensusState) scheduleRound0(height int) { //log.Debug("scheduleRound0", "now", time.Now(), "startTime", cs.StartTime) sleepDuration := cs.StartTime.Sub(time.Now()) go func() { @@ -451,7 +451,7 @@ func (cs *ConsensusState) SetPrivValidator(priv *sm.PrivValidator) { // Enter: `timeoutPrecommits` after any +2/3 precommits from (height,round-1) // Enter: `startTime = commitTime+timeoutCommit` from NewHeight(height) // NOTE: cs.StartTime was already set for height. -func (cs *ConsensusState) EnterNewRound(height uint, round uint) { +func (cs *ConsensusState) EnterNewRound(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && cs.Step != RoundStepNewHeight) { @@ -483,7 +483,7 @@ func (cs *ConsensusState) EnterNewRound(height uint, round uint) { } // Enter: from NewRound(height,round). -func (cs *ConsensusState) EnterPropose(height uint, round uint) { +func (cs *ConsensusState) EnterPropose(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPropose <= cs.Step) { @@ -523,7 +523,7 @@ func (cs *ConsensusState) EnterPropose(height uint, round uint) { } // Decides on the next proposal and sets them onto cs.Proposal* -func (cs *ConsensusState) decideProposal(height uint, round uint) { +func (cs *ConsensusState) decideProposal(height int, round int) { var block *types.Block var blockParts *types.PartSet @@ -561,7 +561,7 @@ func (cs *ConsensusState) isProposalComplete() bool { if cs.Proposal.POLRound < 0 { return true } else { - return cs.Votes.Prevotes(uint(cs.Proposal.POLRound)).HasTwoThirdsMajority() + return cs.Votes.Prevotes(cs.Proposal.POLRound).HasTwoThirdsMajority() } } @@ -588,7 +588,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts Height: cs.Height, Time: time.Now(), Fees: 0, // TODO fees - NumTxs: uint(len(txs)), + NumTxs: len(txs), LastBlockHash: cs.state.LastBlockHash, LastBlockParts: cs.state.LastBlockParts, StateHash: nil, // Will set afterwards. @@ -615,7 +615,7 @@ func (cs *ConsensusState) createProposalBlock() (block *types.Block, blockParts // Enter: any +2/3 prevotes for future round. // Prevote for LockedBlock if we're locked, or ProposalBlock if valid. // Otherwise vote nil. -func (cs *ConsensusState) EnterPrevote(height uint, round uint) { +func (cs *ConsensusState) EnterPrevote(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevote <= cs.Step) { @@ -636,7 +636,7 @@ func (cs *ConsensusState) EnterPrevote(height uint, round uint) { }*/ } -func (cs *ConsensusState) doPrevote(height uint, round uint) { +func (cs *ConsensusState) doPrevote(height int, round int) { // If a block is locked, prevote that. if cs.LockedBlock != nil { log.Debug("EnterPrevote: Block was locked") @@ -666,7 +666,7 @@ func (cs *ConsensusState) doPrevote(height uint, round uint) { } // Enter: any +2/3 prevotes at next round. -func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { +func (cs *ConsensusState) EnterPrevoteWait(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrevoteWait <= cs.Step) { @@ -695,7 +695,7 @@ func (cs *ConsensusState) EnterPrevoteWait(height uint, round uint) { // Lock & precommit the ProposalBlock if we have enough prevotes for it, // else, unlock an existing lock and precommit nil if +2/3 of prevotes were nil, // else, precommit locked block or nil otherwise. -func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { +func (cs *ConsensusState) EnterPrecommit(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommit <= cs.Step) { @@ -768,7 +768,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { // Otherwise, we need to fetch the +2/3 prevoted block. // Unlock and precommit nil. // The +2/3 prevotes for this round is the POL for our unlock. - if cs.Votes.POLRound() < int(round) { + if cs.Votes.POLRound() < round { panic(Fmt("This POLRound shold be %v but got %", round, cs.Votes.POLRound())) } cs.LockedRound = 0 @@ -783,7 +783,7 @@ func (cs *ConsensusState) EnterPrecommit(height uint, round uint) { } // Enter: any +2/3 precommits for next round. -func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { +func (cs *ConsensusState) EnterPrecommitWait(height int, round int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || round < cs.Round || (cs.Round == round && RoundStepPrecommitWait <= cs.Step) { @@ -811,7 +811,7 @@ func (cs *ConsensusState) EnterPrecommitWait(height uint, round uint) { } // Enter: +2/3 precommits for block -func (cs *ConsensusState) EnterCommit(height uint) { +func (cs *ConsensusState) EnterCommit(height int) { cs.mtx.Lock() defer cs.mtx.Unlock() if cs.Height != height || RoundStepCommit <= cs.Step { @@ -865,7 +865,7 @@ func (cs *ConsensusState) EnterCommit(height uint) { } // If we have the block AND +2/3 commits for it, finalize. -func (cs *ConsensusState) tryFinalizeCommit(height uint) { +func (cs *ConsensusState) tryFinalizeCommit(height int) { // SANITY CHECK if cs.Height != height { panic(Fmt("tryFinalizeCommit() cs.Height: %v vs height: %v", cs.Height, height)) @@ -883,7 +883,7 @@ func (cs *ConsensusState) tryFinalizeCommit(height uint) { } // Increment height and goto RoundStepNewHeight -func (cs *ConsensusState) FinalizeCommit(height uint) { +func (cs *ConsensusState) FinalizeCommit(height int) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -950,7 +950,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { // Verify POLRound, which must be -1 or between 0 and proposal.Round exclusive. if proposal.POLRound != -1 && - (proposal.POLRound < 0 || proposal.Round <= uint(proposal.POLRound)) { + (proposal.POLRound < 0 || proposal.Round <= proposal.POLRound) { return ErrInvalidProposalPOLRound } @@ -965,7 +965,7 @@ func (cs *ConsensusState) SetProposal(proposal *Proposal) error { } // NOTE: block is not necessarily valid. -func (cs *ConsensusState) AddProposalBlockPart(height uint, part *types.Part) (added bool, err error) { +func (cs *ConsensusState) AddProposalBlockPart(height int, part *types.Part) (added bool, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -1001,7 +1001,7 @@ func (cs *ConsensusState) AddProposalBlockPart(height uint, part *types.Part) (a return added, nil } -func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { +func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey string) (added bool, index int, err error) { cs.mtx.Lock() defer cs.mtx.Unlock() @@ -1010,7 +1010,7 @@ func (cs *ConsensusState) AddVote(address []byte, vote *types.Vote, peerKey stri //----------------------------------------------------------------------------- -func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey string) (added bool, index uint, err error) { +func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey string) (added bool, index int, err error) { // A precommit for the previous height? if vote.Height+1 == cs.Height && vote.Type == types.VoteTypePrecommit { added, index, err = cs.LastCommit.AddByAddress(address, vote) @@ -1056,8 +1056,7 @@ func (cs *ConsensusState) addVote(address []byte, vote *types.Vote, peerKey stri cs.EnterPrevoteWait(height, vote.Round) } }() - } else if cs.Proposal != nil && - 0 <= cs.Proposal.POLRound && uint(cs.Proposal.POLRound) == vote.Round { + } else if cs.Proposal != nil && 0 <= cs.Proposal.POLRound && cs.Proposal.POLRound == vote.Round { // If the proposal is now complete, enter prevote of cs.Round. if cs.isProposalComplete() { go cs.EnterPrevote(height, cs.Round) diff --git a/consensus/test.go b/consensus/test.go index 397befa0..c105f156 100644 --- a/consensus/test.go +++ b/consensus/test.go @@ -18,7 +18,7 @@ func randConsensusState() (*ConsensusState, []*sm.PrivValidator) { return cs, privValidators } -func randVoteSet(height uint, round uint, type_ byte, numValidators int, votingPower uint64) (*VoteSet, *sm.ValidatorSet, []*sm.PrivValidator) { +func randVoteSet(height int, round int, type_ byte, numValidators int, votingPower int64) (*VoteSet, *sm.ValidatorSet, []*sm.PrivValidator) { vals := make([]*sm.Validator, numValidators) privValidators := make([]*sm.PrivValidator, numValidators) for i := 0; i < numValidators; i++ { diff --git a/consensus/types/proposal.go b/consensus/types/proposal.go index a3a9f546..1c7d0d70 100644 --- a/consensus/types/proposal.go +++ b/consensus/types/proposal.go @@ -17,14 +17,14 @@ var ( ) type Proposal struct { - Height uint `json:"height"` - Round uint `json:"round"` + Height int `json:"height"` + Round int `json:"round"` BlockPartsHeader types.PartSetHeader `json:"block_parts_header"` POLRound int `json:"pol_round"` // -1 if null. Signature account.SignatureEd25519 `json:"signature"` } -func NewProposal(height uint, round uint, blockPartsHeader types.PartSetHeader, polRound int) *Proposal { +func NewProposal(height int, round int, blockPartsHeader types.PartSetHeader, polRound int) *Proposal { return &Proposal{ Height: height, Round: round, diff --git a/consensus/vote_set.go b/consensus/vote_set.go index 2227cfe9..c5c0ae0c 100644 --- a/consensus/vote_set.go +++ b/consensus/vote_set.go @@ -19,23 +19,23 @@ import ( // A commit of prior rounds can be added added in lieu of votes/precommits. // NOTE: Assumes that the sum total of voting power does not exceed MaxUInt64. type VoteSet struct { - height uint - round uint + height int + round int type_ byte mtx sync.Mutex valSet *sm.ValidatorSet - votes []*types.Vote // validator index -> vote - votesBitArray *BitArray // validator index -> has vote? - votesByBlock map[string]uint64 // string(blockHash)+string(blockParts) -> vote sum. - totalVotes uint64 + votes []*types.Vote // validator index -> vote + votesBitArray *BitArray // validator index -> has vote? + votesByBlock map[string]int64 // string(blockHash)+string(blockParts) -> vote sum. + totalVotes int64 maj23Hash []byte maj23Parts types.PartSetHeader maj23Exists bool } // Constructs a new VoteSet struct used to accumulate votes for given height/round. -func NewVoteSet(height uint, round uint, type_ byte, valSet *sm.ValidatorSet) *VoteSet { +func NewVoteSet(height int, round int, type_ byte, valSet *sm.ValidatorSet) *VoteSet { if height == 0 { panic("Cannot make VoteSet for height == 0, doesn't make sense.") } @@ -46,12 +46,12 @@ func NewVoteSet(height uint, round uint, type_ byte, valSet *sm.ValidatorSet) *V valSet: valSet, votes: make([]*types.Vote, valSet.Size()), votesBitArray: NewBitArray(valSet.Size()), - votesByBlock: make(map[string]uint64), + votesByBlock: make(map[string]int64), totalVotes: 0, } } -func (voteSet *VoteSet) Height() uint { +func (voteSet *VoteSet) Height() int { if voteSet == nil { return 0 } else { @@ -59,7 +59,7 @@ func (voteSet *VoteSet) Height() uint { } } -func (voteSet *VoteSet) Round() uint { +func (voteSet *VoteSet) Round() int { if voteSet == nil { return 0 } else { @@ -67,7 +67,7 @@ func (voteSet *VoteSet) Round() uint { } } -func (voteSet *VoteSet) Size() uint { +func (voteSet *VoteSet) Size() int { if voteSet == nil { return 0 } else { @@ -79,7 +79,7 @@ func (voteSet *VoteSet) Size() uint { // Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] // Duplicate votes return added=false, err=nil. // NOTE: vote should not be mutated after adding. -func (voteSet *VoteSet) AddByIndex(valIndex uint, vote *types.Vote) (added bool, index uint, err error) { +func (voteSet *VoteSet) AddByIndex(valIndex int, vote *types.Vote) (added bool, index int, err error) { voteSet.mtx.Lock() defer voteSet.mtx.Unlock() @@ -90,7 +90,7 @@ func (voteSet *VoteSet) AddByIndex(valIndex uint, vote *types.Vote) (added bool, // Otherwise returns err=ErrVote[UnexpectedStep|InvalidAccount|InvalidSignature|InvalidBlockHash|ConflictingSignature] // Duplicate votes return added=false, err=nil. // NOTE: vote should not be mutated after adding. -func (voteSet *VoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index uint, err error) { +func (voteSet *VoteSet) AddByAddress(address []byte, vote *types.Vote) (added bool, index int, err error) { voteSet.mtx.Lock() defer voteSet.mtx.Unlock() @@ -103,7 +103,7 @@ func (voteSet *VoteSet) AddByAddress(address []byte, vote *types.Vote) (added bo return voteSet.addVote(val, valIndex, vote) } -func (voteSet *VoteSet) addByIndex(valIndex uint, vote *types.Vote) (bool, uint, error) { +func (voteSet *VoteSet) addByIndex(valIndex int, vote *types.Vote) (bool, int, error) { // Ensure that signer is a validator. _, val := voteSet.valSet.GetByIndex(valIndex) if val == nil { @@ -113,7 +113,7 @@ func (voteSet *VoteSet) addByIndex(valIndex uint, vote *types.Vote) (bool, uint, return voteSet.addVote(val, valIndex, vote) } -func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex uint, vote *types.Vote) (bool, uint, error) { +func (voteSet *VoteSet) addVote(val *sm.Validator, valIndex int, vote *types.Vote) (bool, int, error) { // Make sure the step matches. (or that vote is commit && round < voteSet.round) if (vote.Height != voteSet.height) || @@ -168,7 +168,7 @@ func (voteSet *VoteSet) BitArray() *BitArray { return voteSet.votesBitArray.Copy() } -func (voteSet *VoteSet) GetByIndex(valIndex uint) *types.Vote { +func (voteSet *VoteSet) GetByIndex(valIndex int) *types.Vote { voteSet.mtx.Lock() defer voteSet.mtx.Unlock() return voteSet.votes[valIndex] @@ -264,7 +264,7 @@ func (voteSet *VoteSet) MakeValidation() *types.Validation { panic("Cannot MakeValidation() unless a blockhash has +2/3") } precommits := make([]*types.Vote, voteSet.valSet.Size()) - voteSet.valSet.Iterate(func(valIndex uint, val *sm.Validator) bool { + voteSet.valSet.Iterate(func(valIndex int, val *sm.Validator) bool { vote := voteSet.votes[valIndex] if vote == nil { return false diff --git a/consensus/vote_set_test.go b/consensus/vote_set_test.go index 46bafed6..d6daab19 100644 --- a/consensus/vote_set_test.go +++ b/consensus/vote_set_test.go @@ -15,14 +15,14 @@ import ( // NOTE: see consensus/test.go for common test methods. // Convenience: Return new vote with different height -func withHeight(vote *types.Vote, height uint) *types.Vote { +func withHeight(vote *types.Vote, height int) *types.Vote { vote = vote.Copy() vote.Height = height return vote } // Convenience: Return new vote with different round -func withRound(vote *types.Vote, round uint) *types.Vote { +func withRound(vote *types.Vote, round int) *types.Vote { vote = vote.Copy() vote.Round = round return vote @@ -56,7 +56,7 @@ func signAddVote(privVal *sm.PrivValidator, vote *types.Vote, voteSet *VoteSet) } func TestAddVote(t *testing.T) { - height, round := uint(1), uint(0) + height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) val0 := privValidators[0] @@ -89,7 +89,7 @@ func TestAddVote(t *testing.T) { } func Test2_3Majority(t *testing.T) { - height, round := uint(1), uint(0) + height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: nil} @@ -123,11 +123,11 @@ func Test2_3Majority(t *testing.T) { } func Test2_3MajorityRedux(t *testing.T) { - height, round := uint(1), uint(0) + height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 100, 1) blockHash := CRandBytes(32) - blockPartsTotal := uint(123) + blockPartsTotal := 123 blockParts := types.PartSetHeader{blockPartsTotal, CRandBytes(32)} vote := &types.Vote{Height: height, Round: round, Type: types.VoteTypePrevote, BlockHash: blockHash, BlockParts: blockParts} @@ -190,7 +190,7 @@ func Test2_3MajorityRedux(t *testing.T) { } func TestBadVotes(t *testing.T) { - height, round := uint(1), uint(0) + height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrevote, 10, 1) // val0 votes for nil. @@ -226,7 +226,7 @@ func TestBadVotes(t *testing.T) { } func TestMakeValidation(t *testing.T) { - height, round := uint(1), uint(0) + height, round := 1, 0 voteSet, _, privValidators := randVoteSet(height, round, types.VoteTypePrecommit, 10, 1) blockHash, blockParts := CRandBytes(32), types.PartSetHeader{123, CRandBytes(32)} diff --git a/crawler/crawl.go b/crawler/crawl.go index b337d320..973b27ee 100644 --- a/crawler/crawl.go +++ b/crawler/crawl.go @@ -33,8 +33,8 @@ type Node struct { LastSeen time.Time ChainID string - BlockHeight uint - BlockHistory map[uint]time.Time // when we saw each block + BlockHeight int + BlockHistory map[int]time.Time // when we saw each block NetInfo *rpctypes.ResponseNetInfo Validator bool diff --git a/merkle/iavl_node.go b/merkle/iavl_node.go index 086283a5..6786e9d2 100644 --- a/merkle/iavl_node.go +++ b/merkle/iavl_node.go @@ -13,8 +13,8 @@ import ( type IAVLNode struct { key interface{} value interface{} - height uint8 - size uint + height int8 + size int hash []byte leftHash []byte leftNode *IAVLNode @@ -38,8 +38,8 @@ func ReadIAVLNode(t *IAVLTree, r io.Reader, n *int64, err *error) *IAVLNode { node := &IAVLNode{} // node header - node.height = binary.ReadUint8(r, n, err) - node.size = binary.ReadUvarint(r, n, err) + node.height = binary.ReadInt8(r, n, err) + node.size = binary.ReadVarint(r, n, err) node.key = decodeByteSlice(t.keyCodec, r, n, err) if node.height == 0 { @@ -88,7 +88,7 @@ func (node *IAVLNode) has(t *IAVLTree, key interface{}) (has bool) { } } -func (node *IAVLNode) get(t *IAVLTree, key interface{}) (index uint, value interface{}) { +func (node *IAVLNode) get(t *IAVLTree, key interface{}) (index int, value interface{}) { if node.height == 0 { if t.keyCodec.Compare(node.key, key) == 0 { return 0, node.value @@ -107,7 +107,7 @@ func (node *IAVLNode) get(t *IAVLTree, key interface{}) (index uint, value inter } } -func (node *IAVLNode) getByIndex(t *IAVLTree, index uint) (key interface{}, value interface{}) { +func (node *IAVLNode) getByIndex(t *IAVLTree, index int) (key interface{}, value interface{}) { if node.height == 0 { if index == 0 { return node.key, node.value @@ -127,7 +127,7 @@ func (node *IAVLNode) getByIndex(t *IAVLTree, index uint) (key interface{}, valu } // NOTE: sets hashes recursively -func (node *IAVLNode) hashWithCount(t *IAVLTree) ([]byte, uint) { +func (node *IAVLNode) hashWithCount(t *IAVLTree) ([]byte, int) { if node.hash != nil { return node.hash, 0 } @@ -147,10 +147,10 @@ func (node *IAVLNode) hashWithCount(t *IAVLTree) ([]byte, uint) { } // NOTE: sets hashes recursively -func (node *IAVLNode) writeHashBytes(t *IAVLTree, w io.Writer) (n int64, hashCount uint, err error) { +func (node *IAVLNode) writeHashBytes(t *IAVLTree, w io.Writer) (n int64, hashCount int, err error) { // height & size - binary.WriteUint8(node.height, w, &n, &err) - binary.WriteUvarint(node.size, w, &n, &err) + binary.WriteInt8(node.height, w, &n, &err) + binary.WriteVarint(node.size, w, &n, &err) // key is not written for inner nodes, unlike writePersistBytes if node.height == 0 { @@ -210,8 +210,8 @@ func (node *IAVLNode) save(t *IAVLTree) []byte { // NOTE: sets hashes recursively func (node *IAVLNode) writePersistBytes(t *IAVLTree, w io.Writer) (n int64, err error) { // node header - binary.WriteUint8(node.height, w, &n, &err) - binary.WriteUvarint(node.size, w, &n, &err) + binary.WriteInt8(node.height, w, &n, &err) + binary.WriteVarint(node.size, w, &n, &err) // key (unlike writeHashBytes, key is written for inner nodes) encodeByteSlice(node.key, t.keyCodec, w, &n, &err) @@ -365,7 +365,7 @@ func (node *IAVLNode) rotateLeft(t *IAVLTree) *IAVLNode { // NOTE: mutates height and size func (node *IAVLNode) calcHeightAndSize(t *IAVLTree) { - node.height = maxUint8(node.getLeftNode(t).height, node.getRightNode(t).height) + 1 + node.height = maxInt8(node.getLeftNode(t).height, node.getRightNode(t).height) + 1 node.size = node.getLeftNode(t).size + node.getRightNode(t).size } diff --git a/merkle/iavl_proof.go b/merkle/iavl_proof.go index b3e3b2a0..4d1b11ab 100644 --- a/merkle/iavl_proof.go +++ b/merkle/iavl_proof.go @@ -35,8 +35,8 @@ func (proof *IAVLProof) Verify(keyBytes, valueBytes, rootHash []byte) bool { } type IAVLProofInnerNode struct { - Height uint8 - Size uint + Height int8 + Size int Left []byte Right []byte } @@ -45,8 +45,8 @@ func (branch IAVLProofInnerNode) Hash(childHash []byte) []byte { hasher := sha256.New() buf := new(bytes.Buffer) n, err := int64(0), error(nil) - binary.WriteUint8(branch.Height, buf, &n, &err) - binary.WriteUvarint(branch.Size, buf, &n, &err) + binary.WriteInt8(branch.Height, buf, &n, &err) + binary.WriteVarint(branch.Size, buf, &n, &err) if branch.Left == nil { binary.WriteByteSlice(childHash, buf, &n, &err) binary.WriteByteSlice(branch.Right, buf, &n, &err) @@ -71,8 +71,8 @@ func (leaf IAVLProofLeafNode) Hash() []byte { hasher := sha256.New() buf := new(bytes.Buffer) n, err := int64(0), error(nil) - binary.WriteUint8(0, buf, &n, &err) - binary.WriteUvarint(1, buf, &n, &err) + binary.WriteInt8(0, buf, &n, &err) + binary.WriteVarint(1, buf, &n, &err) binary.WriteByteSlice(leaf.KeyBytes, buf, &n, &err) binary.WriteByteSlice(leaf.ValueBytes, buf, &n, &err) if err != nil { diff --git a/merkle/iavl_test.go b/merkle/iavl_test.go index e92bf4dd..ae374773 100644 --- a/merkle/iavl_test.go +++ b/merkle/iavl_test.go @@ -60,7 +60,7 @@ func P(n *IAVLNode) string { func TestUnit(t *testing.T) { - expectHash := func(tree *IAVLTree, hashCount uint) { + expectHash := func(tree *IAVLTree, hashCount int) { // ensure number of new hash calculations is as expected. hash, count := tree.HashWithCount() if count != hashCount { @@ -78,7 +78,7 @@ func TestUnit(t *testing.T) { } } - expectSet := func(tree *IAVLTree, i int, repr string, hashCount uint) { + expectSet := func(tree *IAVLTree, i int, repr string, hashCount int) { origNode := tree.root updated := tree.Set(i, "") // ensure node was added & structure is as expected. @@ -91,7 +91,7 @@ func TestUnit(t *testing.T) { tree.root = origNode } - expectRemove := func(tree *IAVLTree, i int, repr string, hashCount uint) { + expectRemove := func(tree *IAVLTree, i int, repr string, hashCount int) { origNode := tree.root value, removed := tree.Remove(i) // ensure node was added & structure is as expected. @@ -168,7 +168,7 @@ func TestIntegration(t *testing.T) { if !updated { t.Error("should have been updated") } - if tree.Size() != uint(i+1) { + if tree.Size() != i+1 { t.Error("size was wrong", tree.Size(), i+1) } } @@ -203,7 +203,7 @@ func TestIntegration(t *testing.T) { t.Error("wrong value") } } - if tree.Size() != uint(len(records)-(i+1)) { + if tree.Size() != len(records)-(i+1) { t.Error("size was wrong", tree.Size(), (len(records) - (i + 1))) } } @@ -318,7 +318,7 @@ func BenchmarkImmutableAvlTree(b *testing.B) { // 23000ns/op, 43000ops/s // for i := 0; i < 10000000; i++ { for i := 0; i < 1000000; i++ { - t.Set(RandUint64(), "") + t.Set(RandInt64(), "") } fmt.Println("ok, starting") @@ -327,7 +327,7 @@ func BenchmarkImmutableAvlTree(b *testing.B) { b.StartTimer() for i := 0; i < b.N; i++ { - ri := RandUint64() + ri := RandInt64() t.Set(ri, "") t.Remove(ri) } diff --git a/merkle/iavl_tree.go b/merkle/iavl_tree.go index 66c2ea91..144ae9b6 100644 --- a/merkle/iavl_tree.go +++ b/merkle/iavl_tree.go @@ -68,14 +68,14 @@ func (t *IAVLTree) Copy() Tree { } } -func (t *IAVLTree) Size() uint { +func (t *IAVLTree) Size() int { if t.root == nil { return 0 } return t.root.size } -func (t *IAVLTree) Height() uint8 { +func (t *IAVLTree) Height() int8 { if t.root == nil { return 0 } @@ -106,7 +106,7 @@ func (t *IAVLTree) Hash() []byte { return hash } -func (t *IAVLTree) HashWithCount() ([]byte, uint) { +func (t *IAVLTree) HashWithCount() ([]byte, int) { if t.root == nil { return nil, 0 } @@ -130,14 +130,14 @@ func (t *IAVLTree) Load(hash []byte) { } } -func (t *IAVLTree) Get(key interface{}) (index uint, value interface{}) { +func (t *IAVLTree) Get(key interface{}) (index int, value interface{}) { if t.root == nil { return 0, nil } return t.root.get(t, key) } -func (t *IAVLTree) GetByIndex(index uint) (key interface{}, value interface{}) { +func (t *IAVLTree) GetByIndex(index int) (key interface{}, value interface{}) { if t.root == nil { return nil, nil } diff --git a/merkle/simple_tree.go b/merkle/simple_tree.go index 73465008..941ddb5c 100644 --- a/merkle/simple_tree.go +++ b/merkle/simple_tree.go @@ -90,8 +90,8 @@ func SimpleHashFromHashables(items []Hashable) []byte { //-------------------------------------------------------------------------------- type SimpleProof struct { - Index uint `json:"index"` - Total uint `json:"total"` + Index int `json:"index"` + Total int `json:"total"` LeafHash []byte `json:"leaf_hash"` InnerHashes [][]byte `json:"inner_hashes"` // Hashes from leaf's sibling to a root's child. RootHash []byte `json:"root_hash"` @@ -103,8 +103,8 @@ func SimpleProofsFromHashables(items []Hashable) (proofs []*SimpleProof) { proofs = make([]*SimpleProof, len(items)) for i, trail := range trails { proofs[i] = &SimpleProof{ - Index: uint(i), - Total: uint(len(items)), + Index: i, + Total: len(items), LeafHash: trail.Hash, InnerHashes: trail.FlattenInnerHashes(), RootHash: root.Hash, @@ -154,7 +154,7 @@ func (sp *SimpleProof) StringIndented(indent string) string { // Use the leafHash and innerHashes to get the root merkle hash. // If the length of the innerHashes slice isn't exactly correct, the result is nil. -func computeHashFromInnerHashes(index uint, total uint, leafHash []byte, innerHashes [][]byte) []byte { +func computeHashFromInnerHashes(index int, total int, leafHash []byte, innerHashes [][]byte) []byte { // Recursive impl. if index >= total { return nil diff --git a/merkle/simple_tree_test.go b/merkle/simple_tree_test.go index eabf8f32..5cbf732e 100644 --- a/merkle/simple_tree_test.go +++ b/merkle/simple_tree_test.go @@ -15,10 +15,10 @@ func (tI testItem) Hash() []byte { func TestSimpleProof(t *testing.T) { - numItems := uint(100) + numItems := 100 items := make([]Hashable, numItems) - for i := uint(0); i < numItems; i++ { + for i := 0; i < numItems; i++ { items[i] = testItem(RandBytes(32)) } diff --git a/merkle/types.go b/merkle/types.go index 68a46131..87f716c7 100644 --- a/merkle/types.go +++ b/merkle/types.go @@ -1,14 +1,14 @@ package merkle type Tree interface { - Size() (size uint) - Height() (height uint8) + Size() (size int) + Height() (height int8) Has(key interface{}) (has bool) - Get(key interface{}) (index uint, value interface{}) - GetByIndex(index uint) (key interface{}, value interface{}) + Get(key interface{}) (index int, value interface{}) + GetByIndex(index int) (key interface{}, value interface{}) Set(key interface{}, value interface{}) (updated bool) Remove(key interface{}) (value interface{}, removed bool) - HashWithCount() (hash []byte, count uint) + HashWithCount() (hash []byte, count int) Hash() (hash []byte) Save() (hash []byte) Load(hash []byte) diff --git a/merkle/util.go b/merkle/util.go index edc3fbf7..89fd2741 100644 --- a/merkle/util.go +++ b/merkle/util.go @@ -35,7 +35,7 @@ func printIAVLNode(node *IAVLNode, indent int) { } -func maxUint8(a, b uint8) uint8 { +func maxInt8(a, b int8) int8 { if a > b { return a } diff --git a/p2p/addrbook.go b/p2p/addrbook.go index e558c83b..99aa5cef 100644 --- a/p2p/addrbook.go +++ b/p2p/addrbook.go @@ -715,7 +715,7 @@ func groupKey(na *NetAddress) string { type knownAddress struct { Addr *NetAddress Src *NetAddress - Attempts uint32 + Attempts int32 LastAttempt time.Time LastSuccess time.Time BucketType byte diff --git a/p2p/connection.go b/p2p/connection.go index 17912925..856f40f3 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -441,9 +441,9 @@ FOR_LOOP: type ChannelDescriptor struct { Id byte - Priority uint - SendQueueCapacity uint - RecvBufferCapacity uint + Priority int + SendQueueCapacity int + RecvBufferCapacity int } func (chDesc *ChannelDescriptor) FillDefaults() { @@ -462,10 +462,10 @@ type Channel struct { desc *ChannelDescriptor id byte sendQueue chan []byte - sendQueueSize uint32 // atomic. + sendQueueSize int32 // atomic. recving []byte sending []byte - priority uint + priority int recentlySent int64 // exponential moving average } @@ -494,7 +494,7 @@ func (ch *Channel) sendBytes(bytes []byte) bool { // timeout return false case ch.sendQueue <- bytes: - atomic.AddUint32(&ch.sendQueueSize, 1) + atomic.AddInt32(&ch.sendQueueSize, 1) return true } } @@ -505,7 +505,7 @@ func (ch *Channel) sendBytes(bytes []byte) bool { func (ch *Channel) trySendBytes(bytes []byte) bool { select { case ch.sendQueue <- bytes: - atomic.AddUint32(&ch.sendQueueSize, 1) + atomic.AddInt32(&ch.sendQueueSize, 1) return true default: return false @@ -514,7 +514,7 @@ func (ch *Channel) trySendBytes(bytes []byte) bool { // Goroutine-safe func (ch *Channel) loadSendQueueSize() (size int) { - return int(atomic.LoadUint32(&ch.sendQueueSize)) + return int(atomic.LoadInt32(&ch.sendQueueSize)) } // Goroutine-safe @@ -545,7 +545,7 @@ func (ch *Channel) nextMsgPacket() msgPacket { if len(ch.sending) <= maxMsgPacketSize { packet.EOF = byte(0x01) ch.sending = nil - atomic.AddUint32(&ch.sendQueueSize, ^uint32(0)) // decrement sendQueueSize + atomic.AddInt32(&ch.sendQueueSize, -1) // decrement sendQueueSize } else { packet.EOF = byte(0x00) ch.sending = ch.sending[MinInt(maxMsgPacketSize, len(ch.sending)):] diff --git a/rpc/core/accounts.go b/rpc/core/accounts.go index e102e1b7..167f0ca5 100644 --- a/rpc/core/accounts.go +++ b/rpc/core/accounts.go @@ -44,7 +44,7 @@ func GetStorage(address, key []byte) (*ctypes.ResponseGetStorage, error) { } func ListAccounts() (*ctypes.ResponseListAccounts, error) { - var blockHeight uint + var blockHeight int var accounts []*acm.Account state := consensusState.GetState() blockHeight = state.LastBlockHeight diff --git a/rpc/core/blocks.go b/rpc/core/blocks.go index 4479e926..3a5bbdfb 100644 --- a/rpc/core/blocks.go +++ b/rpc/core/blocks.go @@ -9,14 +9,14 @@ import ( //----------------------------------------------------------------------------- -func BlockchainInfo(minHeight, maxHeight uint) (*ctypes.ResponseBlockchainInfo, error) { +func BlockchainInfo(minHeight, maxHeight int) (*ctypes.ResponseBlockchainInfo, error) { if maxHeight == 0 { maxHeight = blockStore.Height() } else { - maxHeight = MinUint(blockStore.Height(), maxHeight) + maxHeight = MinInt(blockStore.Height(), maxHeight) } if minHeight == 0 { - minHeight = uint(MaxInt(1, int(maxHeight)-20)) + minHeight = MaxInt(1, maxHeight-20) } log.Debug("BlockchainInfoHandler", "maxHeight", maxHeight, "minHeight", minHeight) @@ -31,7 +31,7 @@ func BlockchainInfo(minHeight, maxHeight uint) (*ctypes.ResponseBlockchainInfo, //----------------------------------------------------------------------------- -func GetBlock(height uint) (*ctypes.ResponseGetBlock, error) { +func GetBlock(height int) (*ctypes.ResponseGetBlock, error) { if height == 0 { return nil, fmt.Errorf("height must be greater than 0") } diff --git a/rpc/core/consensus.go b/rpc/core/consensus.go index 059bc64a..b97c6155 100644 --- a/rpc/core/consensus.go +++ b/rpc/core/consensus.go @@ -8,17 +8,17 @@ import ( ) func ListValidators() (*ctypes.ResponseListValidators, error) { - var blockHeight uint + var blockHeight int var bondedValidators []*sm.Validator var unbondingValidators []*sm.Validator state := consensusState.GetState() blockHeight = state.LastBlockHeight - state.BondedValidators.Iterate(func(index uint, val *sm.Validator) bool { + state.BondedValidators.Iterate(func(index int, val *sm.Validator) bool { bondedValidators = append(bondedValidators, val) return false }) - state.UnbondingValidators.Iterate(func(index uint, val *sm.Validator) bool { + state.UnbondingValidators.Iterate(func(index int, val *sm.Validator) bool { unbondingValidators = append(unbondingValidators, val) return false }) diff --git a/rpc/core/mempool.go b/rpc/core/mempool.go index c9e83b3d..140cf099 100644 --- a/rpc/core/mempool.go +++ b/rpc/core/mempool.go @@ -23,7 +23,7 @@ func BroadcastTx(tx types.Tx) (*ctypes.Receipt, error) { if callTx, ok := tx.(*types.CallTx); ok { if len(callTx.Address) == 0 { createsContract = 1 - contractAddr = state.NewContractAddress(callTx.Input.Address, uint64(callTx.Input.Sequence)) + contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) } } return &ctypes.Receipt{txHash, createsContract, contractAddr}, nil diff --git a/rpc/core/names.go b/rpc/core/names.go index 7f0d872e..01c7cef1 100644 --- a/rpc/core/names.go +++ b/rpc/core/names.go @@ -17,7 +17,7 @@ func GetName(name string) (*types.NameRegEntry, error) { } func ListNames() (*ctypes.ResponseListNames, error) { - var blockHeight uint + var blockHeight int var names []*types.NameRegEntry state := consensusState.GetState() blockHeight = state.LastBlockHeight diff --git a/rpc/core/txs.go b/rpc/core/txs.go index be269f47..8a80cd4e 100644 --- a/rpc/core/txs.go +++ b/rpc/core/txs.go @@ -15,7 +15,7 @@ func toVMAccount(acc *account.Account) *vm.Account { Address: LeftPadWord256(acc.Address), Balance: acc.Balance, Code: acc.Code, // This is crazy. - Nonce: uint64(acc.Sequence), + Nonce: int64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), Other: acc.PubKey, } @@ -36,14 +36,14 @@ func Call(address, data []byte) (*ctypes.ResponseCall, error) { caller := &vm.Account{Address: Zero256} txCache := state.NewTxCache(cache) params := vm.Params{ - BlockHeight: uint64(st.LastBlockHeight), + BlockHeight: int64(st.LastBlockHeight), BlockHash: LeftPadWord256(st.LastBlockHash), BlockTime: st.LastBlockTime.Unix(), GasLimit: 10000000, } vmach := vm.NewVM(txCache, params, caller.Address, nil) - gas := uint64(1000000000) + gas := int64(1000000000) ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) if err != nil { return nil, err @@ -61,14 +61,14 @@ func CallCode(code, data []byte) (*ctypes.ResponseCall, error) { caller := &vm.Account{Address: Zero256} txCache := state.NewTxCache(cache) params := vm.Params{ - BlockHeight: uint64(st.LastBlockHeight), + BlockHeight: int64(st.LastBlockHeight), BlockHash: LeftPadWord256(st.LastBlockHash), BlockTime: st.LastBlockTime.Unix(), GasLimit: 10000000, } vmach := vm.NewVM(txCache, params, caller.Address, nil) - gas := uint64(1000000000) + gas := int64(1000000000) ret, err := vmach.Call(caller, callee, code, data, 0, &gas) if err != nil { return nil, err diff --git a/rpc/core/types/responses.go b/rpc/core/types/responses.go index 221b9562..02dc267d 100644 --- a/rpc/core/types/responses.go +++ b/rpc/core/types/responses.go @@ -13,12 +13,12 @@ type ResponseGetStorage struct { type ResponseCall struct { Return []byte `json:"return"` - GasUsed uint64 `json:"gas_used"` + GasUsed int64 `json:"gas_used"` // TODO ... } type ResponseListAccounts struct { - BlockHeight uint `json:"block_height"` + BlockHeight int `json:"block_height"` Accounts []*account.Account `json:"accounts"` } @@ -33,7 +33,7 @@ type ResponseDumpStorage struct { } type ResponseBlockchainInfo struct { - LastHeight uint `json:"last_height"` + LastHeight int `json:"last_height"` BlockMetas []*types.BlockMeta `json:"block_metas"` } @@ -55,7 +55,7 @@ type ResponseStatus struct { GenesisHash []byte `json:"genesis_hash"` PubKey account.PubKey `json:"pub_key"` LatestBlockHash []byte `json:"latest_block_hash"` - LatestBlockHeight uint `json:"latest_block_height"` + LatestBlockHeight int `json:"latest_block_height"` LatestBlockTime int64 `json:"latest_block_time"` // nano } @@ -71,7 +71,7 @@ type Peer struct { } type ResponseListValidators struct { - BlockHeight uint `json:"block_height"` + BlockHeight int `json:"block_height"` BondedValidators []*sm.Validator `json:"bonded_validators"` UnbondingValidators []*sm.Validator `json:"unbonding_validators"` } @@ -82,6 +82,6 @@ type ResponseDumpConsensusState struct { } type ResponseListNames struct { - BlockHeight uint `json:"block_height"` + BlockHeight int `json:"block_height"` Names []*types.NameRegEntry `json:"names"` } diff --git a/rpc/server/handlers.go b/rpc/server/handlers.go index 2180a06b..77ecc5c9 100644 --- a/rpc/server/handlers.go +++ b/rpc/server/handlers.go @@ -224,7 +224,7 @@ type WSConnection struct { wsConn *websocket.Conn writeChan chan WSResponse quitChan chan struct{} - failedSends uint + failedSends int started uint32 stopped uint32 diff --git a/rpc/test/client_ws_test.go b/rpc/test/client_ws_test.go index a310871b..2ea16c95 100644 --- a/rpc/test/client_ws_test.go +++ b/rpc/test/client_ws_test.go @@ -51,7 +51,7 @@ func TestWSBlockchainGrowth(t *testing.T) { // send a transaction and validate the events from listening for both sender and receiver func TestWSSend(t *testing.T) { toAddr := user[1].Address - amt := uint64(100) + amt := int64(100) con := newWSCon(t) eidInput := types.EventStringAccInput(user[0].Address) @@ -79,7 +79,7 @@ func TestWSDoubleFire(t *testing.T) { unsubscribe(t, con, eid) con.Close() }() - amt := uint64(100) + amt := int64(100) toAddr := user[1].Address // broadcast the transaction, wait to hear about it waitForEvent(t, con, eid, true, func() { @@ -104,7 +104,7 @@ func TestWSCallWait(t *testing.T) { unsubscribe(t, con, eid1) con.Close() }() - amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) + amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, returnCode, returnVal := simpleContract() var contractAddr []byte // wait for the contract to be created @@ -115,7 +115,7 @@ func TestWSCallWait(t *testing.T) { }, unmarshalValidateCall(amt, returnCode)) // susbscribe to the new contract - amt = uint64(10001) + amt = int64(10001) eid2 := types.EventStringAccOutput(contractAddr) subscribe(t, con, eid2) defer func() { @@ -134,7 +134,7 @@ func TestWSCallWait(t *testing.T) { // and validate return func TestWSCallNoWait(t *testing.T) { con := newWSCon(t) - amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) + amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, _, returnVal := simpleContract() tx := makeDefaultCallTx(t, wsTyp, nil, code, amt, gasLim, fee) @@ -142,7 +142,7 @@ func TestWSCallNoWait(t *testing.T) { contractAddr := receipt.ContractAddr // susbscribe to the new contract - amt = uint64(10001) + amt = int64(10001) eid := types.EventStringAccOutput(contractAddr) subscribe(t, con, eid) defer func() { @@ -160,7 +160,7 @@ func TestWSCallNoWait(t *testing.T) { // create two contracts, one of which calls the other func TestWSCallCall(t *testing.T) { con := newWSCon(t) - amt, gasLim, fee := uint64(10000), uint64(1000), uint64(1000) + amt, gasLim, fee := int64(10000), int64(1000), int64(1000) code, _, returnVal := simpleContract() txid := new([]byte) @@ -175,7 +175,7 @@ func TestWSCallCall(t *testing.T) { contractAddr2 := receipt.ContractAddr // susbscribe to the new contracts - amt = uint64(10001) + amt = int64(10001) eid1 := types.EventStringAccReceive(contractAddr1) subscribe(t, con, eid1) defer func() { diff --git a/rpc/test/helpers.go b/rpc/test/helpers.go index e794ef29..87f905f4 100644 --- a/rpc/test/helpers.go +++ b/rpc/test/helpers.go @@ -88,7 +88,7 @@ func init() { //------------------------------------------------------------------------------- // some default transaction functions -func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt uint64) *types.SendTx { +func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt int64) *types.SendTx { nonce := getNonce(t, typ, user[0].Address) tx := types.NewSendTx() tx.AddInputWithNonce(user[0].PubKey, amt, nonce+1) @@ -96,20 +96,20 @@ func makeDefaultSendTx(t *testing.T, typ string, addr []byte, amt uint64) *types return tx } -func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt uint64) *types.SendTx { +func makeDefaultSendTxSigned(t *testing.T, typ string, addr []byte, amt int64) *types.SendTx { tx := makeDefaultSendTx(t, typ, addr, amt) tx.SignInput(chainID, 0, user[0]) return tx } -func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee uint64) *types.CallTx { +func makeDefaultCallTx(t *testing.T, typ string, addr, code []byte, amt, gasLim, fee int64) *types.CallTx { nonce := getNonce(t, typ, user[0].Address) tx := types.NewCallTxWithNonce(user[0].PubKey, addr, code, amt, gasLim, fee, nonce+1) tx.Sign(chainID, user[0]) return tx } -func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee uint64) *types.NameTx { +func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee int64) *types.NameTx { nonce := getNonce(t, typ, user[0].Address) tx := types.NewNameTxWithNonce(user[0].PubKey, name, value, amt, fee, nonce+1) tx.Sign(chainID, user[0]) @@ -120,7 +120,7 @@ func makeDefaultNameTx(t *testing.T, typ string, name, value string, amt, fee ui // rpc call wrappers (fail on err) // get an account's nonce -func getNonce(t *testing.T, typ string, addr []byte) uint { +func getNonce(t *testing.T, typ string, addr []byte) int { client := clients[typ] ac, err := client.GetAccount(addr) if err != nil { diff --git a/rpc/test/tests.go b/rpc/test/tests.go index b98914cb..215eb9b3 100644 --- a/rpc/test/tests.go +++ b/rpc/test/tests.go @@ -45,7 +45,7 @@ func testGetAccount(t *testing.T, typ string) { } func testSignedTx(t *testing.T, typ string) { - amt := uint64(100) + amt := int64(100) toAddr := user[1].Address testOneSignTx(t, typ, toAddr, amt) @@ -56,7 +56,7 @@ func testSignedTx(t *testing.T, typ string) { testOneSignTx(t, typ, toAddr, amt) } -func testOneSignTx(t *testing.T, typ string, addr []byte, amt uint64) { +func testOneSignTx(t *testing.T, typ string, addr []byte, amt int64) { tx := makeDefaultSendTx(t, typ, addr, amt) tx2 := signTx(t, typ, tx, user[0]) tx2hash := account.HashSignBytes(chainID, tx2) @@ -72,7 +72,7 @@ func testOneSignTx(t *testing.T, typ string, addr []byte, amt uint64) { } func testBroadcastTx(t *testing.T, typ string) { - amt := uint64(100) + amt := int64(100) toAddr := user[1].Address tx := makeDefaultSendTxSigned(t, typ, toAddr, amt) receipt := broadcastTx(t, typ, tx) @@ -106,7 +106,7 @@ func testGetStorage(t *testing.T, typ string) { con.Close() }() - amt, gasLim, fee := uint64(1100), uint64(1000), uint64(1000) + amt, gasLim, fee := int64(1100), int64(1000), int64(1000) code := []byte{0x60, 0x5, 0x60, 0x1, 0x55} tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) receipt := broadcastTx(t, typ, tx) @@ -161,7 +161,7 @@ func testCall(t *testing.T, typ string) { client := clients[typ] // create the contract - amt, gasLim, fee := uint64(6969), uint64(1000), uint64(1000) + amt, gasLim, fee := int64(6969), int64(1000), int64(1000) code, _, _ := simpleContract() tx := makeDefaultCallTx(t, typ, nil, code, amt, gasLim, fee) receipt := broadcastTx(t, typ, tx) @@ -203,8 +203,8 @@ func testNameReg(t *testing.T, typ string) { // since entries ought to be unique and these run against different clients, we append the typ name := "ye_old_domain_name_" + typ data := "if not now, when" - fee := uint64(1000) - numDesiredBlocks := uint64(2) + fee := int64(1000) + numDesiredBlocks := int64(2) amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx := makeDefaultNameTx(t, typ, name, data, amt, fee) @@ -221,7 +221,7 @@ func testNameReg(t *testing.T, typ string) { } // update the data as the owner, make sure still there - numDesiredBlocks = uint64(2) + numDesiredBlocks = int64(2) data = "these are amongst the things I wish to bestow upon the youth of generations come: a safe supply of honey, and a better money. For what else shall they need" amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx = makeDefaultNameTx(t, typ, name, data, amt, fee) diff --git a/rpc/test/ws_helpers.go b/rpc/test/ws_helpers.go index 55d074ae..211493d1 100644 --- a/rpc/test/ws_helpers.go +++ b/rpc/test/ws_helpers.go @@ -133,7 +133,7 @@ func unmarshalResponseNewBlock(b []byte) (*types.Block, error) { } func unmarshalValidateBlockchain(t *testing.T, con *websocket.Conn, eid string) { - var initBlockN uint + var initBlockN int for i := 0; i < 2; i++ { waitForEvent(t, con, eid, true, func() {}, func(eid string, b []byte) error { block, err := unmarshalResponseNewBlock(b) @@ -143,7 +143,7 @@ func unmarshalValidateBlockchain(t *testing.T, con *websocket.Conn, eid string) if i == 0 { initBlockN = block.Header.Height } else { - if block.Header.Height != initBlockN+uint(i) { + if block.Header.Height != initBlockN+i { return fmt.Errorf("Expected block %d, got block %d", i, block.Header.Height) } } @@ -153,7 +153,7 @@ func unmarshalValidateBlockchain(t *testing.T, con *websocket.Conn, eid string) } } -func unmarshalValidateSend(amt uint64, toAddr []byte) func(string, []byte) error { +func unmarshalValidateSend(amt int64, toAddr []byte) func(string, []byte) error { return func(eid string, b []byte) error { // unmarshal and assert correctness var response struct { @@ -186,7 +186,7 @@ func unmarshalValidateSend(amt uint64, toAddr []byte) func(string, []byte) error } } -func unmarshalValidateCall(amt uint64, returnCode []byte) func(string, []byte) error { +func unmarshalValidateCall(amt int64, returnCode []byte) func(string, []byte) error { return func(eid string, b []byte) error { // unmarshall and assert somethings var response struct { diff --git a/state/execution.go b/state/execution.go index c68bf4ea..16b051ba 100644 --- a/state/execution.go +++ b/state/execution.go @@ -44,7 +44,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade return errors.New("Block at height 1 (first block) should have no LastValidation precommits") } } else { - if uint(len(block.LastValidation.Precommits)) != s.LastBondedValidators.Size() { + if len(block.LastValidation.Precommits) != s.LastBondedValidators.Size() { return errors.New(Fmt("Invalid block validation size. Expected %v, got %v", s.LastBondedValidators.Size(), len(block.LastValidation.Precommits))) } @@ -60,7 +60,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade if precommit == nil { continue } - _, val := s.LastBondedValidators.GetByIndex(uint(i)) + _, val := s.LastBondedValidators.GetByIndex(i) if val == nil { panic(Fmt("Failed to fetch validator at index %v", i)) } @@ -101,7 +101,7 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // If any unbonding periods are over, // reward account with bonded coins. toRelease := []*Validator{} - s.UnbondingValidators.Iterate(func(index uint, val *Validator) bool { + s.UnbondingValidators.Iterate(func(index int, val *Validator) bool { if val.UnbondHeight+unbondingPeriodBlocks < block.Height { toRelease = append(toRelease, val) } @@ -114,8 +114,8 @@ func execBlock(s *State, block *types.Block, blockPartsHeader types.PartSetHeade // If any validators haven't signed in a while, // unbond them, they have timed out. toTimeout := []*Validator{} - s.BondedValidators.Iterate(func(index uint, val *Validator) bool { - lastActivityHeight := MaxUint(val.BondHeight, val.LastCommitHeight) + s.BondedValidators.Iterate(func(index int, val *Validator) bool { + lastActivityHeight := MaxInt(val.BondHeight, val.LastCommitHeight) if lastActivityHeight+validatorTimeoutBlocks < block.Height { log.Info("Validator timeout", "validator", val, "height", block.Height) toTimeout = append(toTimeout, val) @@ -191,7 +191,7 @@ func checkInputPubKey(acc *account.Account, in *types.TxInput) error { return nil } -func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total uint64, err error) { +func validateInputs(accounts map[string]*account.Account, signBytes []byte, ins []*types.TxInput) (total int64, err error) { for _, in := range ins { acc := accounts[string(in.Address)] if acc == nil { @@ -219,8 +219,8 @@ func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (e // Check sequences if acc.Sequence+1 != in.Sequence { return types.ErrTxInvalidSequence{ - Got: uint64(in.Sequence), - Expected: uint64(acc.Sequence + 1), + Got: in.Sequence, + Expected: acc.Sequence + 1, } } // Check amount @@ -230,7 +230,7 @@ func validateInput(acc *account.Account, signBytes []byte, in *types.TxInput) (e return nil } -func validateOutputs(outs []*types.TxOutput) (total uint64, err error) { +func validateOutputs(outs []*types.TxOutput) (total int64, err error) { for _, out := range outs { // Check TxOutput basic if err := out.ValidateBasic(); err != nil { @@ -271,7 +271,7 @@ func adjustByOutputs(accounts map[string]*account.Account, outs []*types.TxOutpu func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Fireable) error { // TODO: do something with fees - fees := uint64(0) + fees := int64(0) _s := blockCache.State() // hack to access validators and block height // Exec tx @@ -362,14 +362,14 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea if runCall { var ( - gas uint64 = tx.GasLimit + gas int64 = tx.GasLimit err error = nil caller *vm.Account = toVMAccount(inAcc) callee *vm.Account = nil code []byte = nil txCache = NewTxCache(blockCache) params = vm.Params{ - BlockHeight: uint64(_s.LastBlockHeight), + BlockHeight: int64(_s.LastBlockHeight), BlockHash: LeftPadWord256(_s.LastBlockHash), BlockTime: _s.LastBlockTime.Unix(), GasLimit: 10000000, @@ -490,8 +490,8 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea // let's say cost of a name for one block is len(data) + 32 costPerBlock := types.NameCostPerBlock * types.NameCostPerByte * tx.BaseEntryCost() - expiresIn := value / uint64(costPerBlock) - lastBlockHeight := uint64(_s.LastBlockHeight) + expiresIn := int(value / costPerBlock) + lastBlockHeight := _s.LastBlockHeight log.Debug("New NameTx", "value", value, "costPerBlock", costPerBlock, "expiresIn", expiresIn, "lastBlock", lastBlockHeight) @@ -530,9 +530,9 @@ func ExecTx(blockCache *BlockCache, tx_ types.Tx, runCall bool, evc events.Firea } else { // since the size of the data may have changed // we use the total amount of "credit" - oldCredit := (entry.Expires - lastBlockHeight) * types.BaseEntryCost(entry.Name, entry.Data) + oldCredit := int64(entry.Expires-lastBlockHeight) * types.BaseEntryCost(entry.Name, entry.Data) credit := oldCredit + value - expiresIn = credit / costPerBlock + expiresIn = int(credit / costPerBlock) if expiresIn < types.MinNameRegistrationPeriod { return fmt.Errorf("Names must be registered for at least %d blocks", types.MinNameRegistrationPeriod) } diff --git a/state/genesis.go b/state/genesis.go index 2024e211..47576d1e 100644 --- a/state/genesis.go +++ b/state/genesis.go @@ -14,12 +14,12 @@ import ( type GenesisAccount struct { Address []byte `json:"address"` - Amount uint64 `json:"amount"` + Amount int64 `json:"amount"` } type GenesisValidator struct { PubKey account.PubKeyEd25519 `json:"pub_key"` - Amount uint64 `json:"amount"` + Amount int64 `json:"amount"` UnbondTo []GenesisAccount `json:"unbond_to"` } diff --git a/state/priv_validator.go b/state/priv_validator.go index 41c8b913..d88f17b8 100644 --- a/state/priv_validator.go +++ b/state/priv_validator.go @@ -23,7 +23,7 @@ const ( stepPrecommit = 3 ) -func voteToStep(vote *types.Vote) uint8 { +func voteToStep(vote *types.Vote) int8 { switch vote.Type { case types.VoteTypePrevote: return stepPrevote @@ -38,9 +38,9 @@ type PrivValidator struct { Address []byte `json:"address"` PubKey account.PubKeyEd25519 `json:"pub_key"` PrivKey account.PrivKeyEd25519 `json:"priv_key"` - LastHeight uint `json:"last_height"` - LastRound uint `json:"last_round"` - LastStep uint8 `json:"last_step"` + LastHeight int `json:"last_height"` + LastRound int `json:"last_round"` + LastStep int8 `json:"last_step"` // For persistence. // Overloaded for testing. @@ -166,8 +166,8 @@ func (privVal *PrivValidator) SignRebondTx(chainID string, rebondTx *types.Rebon // Persist height/round/step privVal.LastHeight = rebondTx.Height - privVal.LastRound = math.MaxUint64 // We can't do anything else for this rebondTx.Height. - privVal.LastStep = math.MaxUint8 + privVal.LastRound = math.MaxInt64 // We can't do anything else for this rebondTx.Height. + privVal.LastStep = math.MaxInt8 privVal.save() // Sign diff --git a/state/state.go b/state/state.go index e7486b27..c8d5d512 100644 --- a/state/state.go +++ b/state/state.go @@ -16,10 +16,10 @@ import ( var ( stateKey = []byte("stateKey") - minBondAmount = uint64(1) // TODO adjust - defaultAccountsCacheCapacity = 1000 // TODO adjust - unbondingPeriodBlocks = uint(60 * 24 * 365) // TODO probably better to make it time based. - validatorTimeoutBlocks = uint(10) // TODO adjust + minBondAmount = int64(1) // TODO adjust + defaultAccountsCacheCapacity = 1000 // TODO adjust + unbondingPeriodBlocks = int(60 * 24 * 365) // TODO probably better to make it time based. + validatorTimeoutBlocks = int(10) // TODO adjust ) //----------------------------------------------------------------------------- @@ -28,7 +28,7 @@ var ( type State struct { DB dbm.DB ChainID string - LastBlockHeight uint + LastBlockHeight int LastBlockHash []byte LastBlockParts types.PartSetHeader LastBlockTime time.Time @@ -50,7 +50,7 @@ func LoadState(db dbm.DB) *State { } else { r, n, err := bytes.NewReader(buf), new(int64), new(error) s.ChainID = binary.ReadString(r, n, err) - s.LastBlockHeight = binary.ReadUvarint(r, n, err) + s.LastBlockHeight = binary.ReadVarint(r, n, err) s.LastBlockHash = binary.ReadByteSlice(r, n, err) s.LastBlockParts = binary.ReadBinary(types.PartSetHeader{}, r, n, err).(types.PartSetHeader) s.LastBlockTime = binary.ReadTime(r, n, err) @@ -80,7 +80,7 @@ func (s *State) Save() { s.nameReg.Save() buf, n, err := new(bytes.Buffer), new(int64), new(error) binary.WriteString(s.ChainID, buf, n, err) - binary.WriteUvarint(s.LastBlockHeight, buf, n, err) + binary.WriteVarint(s.LastBlockHeight, buf, n, err) binary.WriteByteSlice(s.LastBlockHash, buf, n, err) binary.WriteBinary(s.LastBlockParts, buf, n, err) binary.WriteTime(s.LastBlockTime, buf, n, err) diff --git a/state/state_test.go b/state/state_test.go index 6a221977..7078cea0 100644 --- a/state/state_test.go +++ b/state/state_test.go @@ -81,7 +81,7 @@ func makeBlock(t *testing.T, state *State, validation *types.Validation, txs []t Height: state.LastBlockHeight + 1, Time: state.LastBlockTime.Add(time.Minute), Fees: 0, - NumTxs: uint(len(txs)), + NumTxs: len(txs), LastBlockHash: state.LastBlockHash, LastBlockParts: state.LastBlockParts, StateHash: nil, @@ -179,7 +179,7 @@ func TestTxSequence(t *testing.T) { // Test a variety of sequence numbers for the tx. // The tx should only pass when i == 1. for i := -1; i < 3; i++ { - sequence := acc0.Sequence + uint(i) + sequence := acc0.Sequence + i tx := types.NewSendTx() tx.AddInputWithNonce(acc0PubKey, 1, sequence) tx.AddOutput(acc1.Address, 1) @@ -216,15 +216,15 @@ func TestNameTxs(t *testing.T) { state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) types.MinNameRegistrationPeriod = 5 - startingBlock := uint64(state.LastBlockHeight) + startingBlock := state.LastBlockHeight // try some bad names. these should all fail names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyes", "no spaces please"} data := "something about all this just doesn't feel right." - fee := uint64(1000) - numDesiredBlocks := uint64(5) + fee := int64(1000) + numDesiredBlocks := 5 for _, name := range names { - amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + amt := fee + int64(numDesiredBlocks)*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[0]) @@ -237,7 +237,7 @@ func TestNameTxs(t *testing.T) { name := "hold_it_chum" datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"} for _, data := range datas { - amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + amt := fee + int64(numDesiredBlocks)*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[0]) @@ -246,7 +246,7 @@ func TestNameTxs(t *testing.T) { } } - validateEntry := func(t *testing.T, entry *types.NameRegEntry, name, data string, addr []byte, expires uint64) { + validateEntry := func(t *testing.T, entry *types.NameRegEntry, name, data string, addr []byte, expires int) { if entry == nil { t.Fatalf("Could not find name %s", name) @@ -268,7 +268,7 @@ func TestNameTxs(t *testing.T) { // try a good one, check data, owner, expiry name = "looking_good/karaoke_bar" data = "on this side of neptune there are 1234567890 people: first is OMNIVORE. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')" - amt := fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + amt := fee + int64(numDesiredBlocks)*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ := types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err != nil { @@ -304,7 +304,7 @@ func TestNameTxs(t *testing.T) { validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3) // fail to update it as non-owner - state.LastBlockHeight = uint(entry.Expires - 1) + state.LastBlockHeight = entry.Expires - 1 tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err == nil { @@ -312,27 +312,27 @@ func TestNameTxs(t *testing.T) { } // once expires, non-owner succeeds - state.LastBlockHeight = uint(entry.Expires) + state.LastBlockHeight = entry.Expires tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) // update it as new owner, with new data (longer), but keep the expiry! data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that." oldCredit := amt - fee numDesiredBlocks = 10 - amt = fee + (numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) - oldCredit) + amt = fee + (int64(numDesiredBlocks)*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) - oldCredit) tx, _ = types.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[1]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) + validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) // test removal amt = fee @@ -351,15 +351,15 @@ func TestNameTxs(t *testing.T) { // test removal by key1 after expiry name = "looking_good/karaoke_bar" data = "some data" - amt = fee + numDesiredBlocks*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) + amt = fee + int64(numDesiredBlocks)*types.NameCostPerByte*types.NameCostPerBlock*types.BaseEntryCost(name, data) tx, _ = types.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) tx.Sign(state.ChainID, privAccounts[0]) if err := execTxWithState(state, tx, true); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, uint64(state.LastBlockHeight)+numDesiredBlocks) - state.LastBlockHeight = uint(entry.Expires) + validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks) + state.LastBlockHeight = entry.Expires amt = fee data = "" @@ -474,7 +474,7 @@ attack the network, they'll generate the longest chain and outpace attackers. network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone ` - entryAmount := uint64(10000) + entryAmount := int64(10000) state := state.Copy() tx := &types.NameTx{ diff --git a/state/test.go b/state/test.go index 8e0f3520..a235e762 100644 --- a/state/test.go +++ b/state/test.go @@ -22,27 +22,27 @@ func Tempfile(prefix string) (*os.File, string) { return file, file.Name() } -func RandAccount(randBalance bool, minBalance uint64) (*account.Account, *account.PrivAccount) { +func RandAccount(randBalance bool, minBalance int64) (*account.Account, *account.PrivAccount) { privAccount := account.GenPrivAccount() acc := &account.Account{ Address: privAccount.PubKey.Address(), PubKey: privAccount.PubKey, - Sequence: RandUint(), + Sequence: RandInt(), Balance: minBalance, } if randBalance { - acc.Balance += uint64(RandUint32()) + acc.Balance += int64(RandUint32()) } return acc, privAccount } -func RandValidator(randBonded bool, minBonded uint64) (*ValidatorInfo, *Validator, *PrivValidator) { +func RandValidator(randBonded bool, minBonded int64) (*ValidatorInfo, *Validator, *PrivValidator) { privVal := GenPrivValidator() _, tempFilePath := Tempfile("priv_validator_") privVal.SetFile(tempFilePath) bonded := minBonded if randBonded { - bonded += uint64(RandUint32()) + bonded += int64(RandUint32()) } valInfo := &ValidatorInfo{ Address: privVal.Address, @@ -66,7 +66,7 @@ func RandValidator(randBonded bool, minBonded uint64) (*ValidatorInfo, *Validato return valInfo, val, privVal } -func RandGenesisState(numAccounts int, randBalance bool, minBalance uint64, numValidators int, randBonded bool, minBonded uint64) (*State, []*account.PrivAccount, []*PrivValidator) { +func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*account.PrivAccount, []*PrivValidator) { db := dbm.NewMemDB() accounts := make([]GenesisAccount, numAccounts) privAccounts := make([]*account.PrivAccount, numAccounts) diff --git a/state/tx_cache.go b/state/tx_cache.go index 428959bc..718d6178 100644 --- a/state/tx_cache.go +++ b/state/tx_cache.go @@ -68,7 +68,7 @@ func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { nonce := creator.Nonce creator.Nonce += 1 - addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), nonce)) + addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), int(nonce))) // Create account from address. account, removed := vmUnpack(cache.accounts[addr]) @@ -144,10 +144,10 @@ func (cache *TxCache) AddLog(log *vm.Log) { //----------------------------------------------------------------------------- // Convenience function to return address of new contract -func NewContractAddress(caller []byte, nonce uint64) []byte { +func NewContractAddress(caller []byte, nonce int) []byte { temp := make([]byte, 32+8) copy(temp, caller) - PutUint64BE(temp[32:], nonce) + PutInt64BE(temp[32:], int64(nonce)) return sha3.Sha3(temp)[:20] } @@ -157,7 +157,7 @@ func toVMAccount(acc *ac.Account) *vm.Account { Address: LeftPadWord256(acc.Address), Balance: acc.Balance, Code: acc.Code, // This is crazy. - Nonce: uint64(acc.Sequence), + Nonce: int64(acc.Sequence), StorageRoot: LeftPadWord256(acc.StorageRoot), Other: acc.PubKey, } @@ -180,7 +180,7 @@ func toStateAccount(acc *vm.Account) *ac.Account { PubKey: pubKey, Balance: acc.Balance, Code: acc.Code, - Sequence: uint(acc.Nonce), + Sequence: int(acc.Nonce), StorageRoot: storageRoot, } } diff --git a/state/validator.go b/state/validator.go index 28046d18..46483b59 100644 --- a/state/validator.go +++ b/state/validator.go @@ -15,11 +15,11 @@ type ValidatorInfo struct { Address []byte `json:"address"` PubKey account.PubKeyEd25519 `json:"pub_key"` UnbondTo []*types.TxOutput `json:"unbond_to"` - FirstBondHeight uint `json:"first_bond_height"` - FirstBondAmount uint64 `json:"first_bond_amount"` - DestroyedHeight uint `json:"destroyed_height"` // If destroyed - DestroyedAmount uint64 `json:"destroyed_amount"` // If destroyed - ReleasedHeight uint `json:"released_height"` // If released + FirstBondHeight int `json:"first_bond_height"` + FirstBondAmount int64 `json:"first_bond_amount"` + DestroyedHeight int `json:"destroyed_height"` // If destroyed + DestroyedAmount int64 `json:"destroyed_amount"` // If destroyed + ReleasedHeight int `json:"released_height"` // If released } func (valInfo *ValidatorInfo) Copy() *ValidatorInfo { @@ -48,10 +48,10 @@ var ValidatorInfoCodec = binary.Codec{ type Validator struct { Address []byte `json:"address"` PubKey account.PubKeyEd25519 `json:"pub_key"` - BondHeight uint `json:"bond_height"` - UnbondHeight uint `json:"unbond_height"` - LastCommitHeight uint `json:"last_commit_height"` - VotingPower uint64 `json:"voting_power"` + BondHeight int `json:"bond_height"` + UnbondHeight int `json:"unbond_height"` + LastCommitHeight int `json:"last_commit_height"` + VotingPower int64 `json:"voting_power"` Accum int64 `json:"accum"` } diff --git a/state/validator_set.go b/state/validator_set.go index af626daf..fad0413d 100644 --- a/state/validator_set.go +++ b/state/validator_set.go @@ -28,7 +28,7 @@ type ValidatorSet struct { // cached (unexported) proposer *Validator - totalVotingPower uint64 + totalVotingPower int64 } func NewValidatorSet(vals []*Validator) *ValidatorSet { @@ -43,7 +43,7 @@ func NewValidatorSet(vals []*Validator) *ValidatorSet { } // TODO: mind the overflow when times and votingPower shares too large. -func (valSet *ValidatorSet) IncrementAccum(times uint) { +func (valSet *ValidatorSet) IncrementAccum(times int) { // Add VotingPower * times to each validator and order into heap. validatorsHeap := NewHeap() for _, val := range valSet.Validators { @@ -52,7 +52,7 @@ func (valSet *ValidatorSet) IncrementAccum(times uint) { } // Decrement the validator with most accum, times times. - for i := uint(0); i < times; i++ { + for i := 0; i < times; i++ { mostest := validatorsHeap.Peek().(*Validator) if i == times-1 { valSet.proposer = mostest @@ -82,27 +82,27 @@ func (valSet *ValidatorSet) HasAddress(address []byte) bool { return idx != len(valSet.Validators) && bytes.Compare(valSet.Validators[idx].Address, address) == 0 } -func (valSet *ValidatorSet) GetByAddress(address []byte) (index uint, val *Validator) { +func (valSet *ValidatorSet) GetByAddress(address []byte) (index int, val *Validator) { idx := sort.Search(len(valSet.Validators), func(i int) bool { return bytes.Compare(address, valSet.Validators[i].Address) <= 0 }) if idx != len(valSet.Validators) && bytes.Compare(valSet.Validators[idx].Address, address) == 0 { - return uint(idx), valSet.Validators[idx].Copy() + return idx, valSet.Validators[idx].Copy() } else { return 0, nil } } -func (valSet *ValidatorSet) GetByIndex(index uint) (address []byte, val *Validator) { +func (valSet *ValidatorSet) GetByIndex(index int) (address []byte, val *Validator) { val = valSet.Validators[index] return val.Address, val.Copy() } -func (valSet *ValidatorSet) Size() uint { - return uint(len(valSet.Validators)) +func (valSet *ValidatorSet) Size() int { + return len(valSet.Validators) } -func (valSet *ValidatorSet) TotalVotingPower() uint64 { +func (valSet *ValidatorSet) TotalVotingPower() int64 { if valSet.totalVotingPower == 0 { for _, val := range valSet.Validators { valSet.totalVotingPower += val.VotingPower @@ -190,9 +190,9 @@ func (valSet *ValidatorSet) Remove(address []byte) (val *Validator, removed bool } } -func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { +func (valSet *ValidatorSet) Iterate(fn func(index int, val *Validator) bool) { for i, val := range valSet.Validators { - stop := fn(uint(i), val.Copy()) + stop := fn(i, val.Copy()) if stop { break } @@ -201,15 +201,15 @@ func (valSet *ValidatorSet) Iterate(fn func(index uint, val *Validator) bool) { // Verify that +2/3 of the set had signed the given signBytes func (valSet *ValidatorSet) VerifyValidation(chainID string, - hash []byte, parts types.PartSetHeader, height uint, v *types.Validation) error { - if valSet.Size() != uint(len(v.Precommits)) { + hash []byte, parts types.PartSetHeader, height int, v *types.Validation) error { + if valSet.Size() != len(v.Precommits) { return fmt.Errorf("Invalid validation -- wrong set size: %v vs %v", valSet.Size(), len(v.Precommits)) } if height != v.Height() { return fmt.Errorf("Invalid validation -- wrong height: %v vs %v", height, v.Height()) } - talliedVotingPower := uint64(0) + talliedVotingPower := int64(0) round := v.Round() for idx, precommit := range v.Precommits { @@ -226,7 +226,7 @@ func (valSet *ValidatorSet) VerifyValidation(chainID string, if precommit.Type != types.VoteTypePrecommit { return fmt.Errorf("Invalid validation -- not precommit @ index %v", idx) } - _, val := valSet.GetByIndex(uint(idx)) + _, val := valSet.GetByIndex(idx) // Validate signature precommitSignBytes := account.SignBytes(chainID, precommit) if !val.PubKey.VerifyBytes(precommitSignBytes, precommit.Signature) { @@ -256,7 +256,7 @@ func (valSet *ValidatorSet) String() string { func (valSet *ValidatorSet) StringIndented(indent string) string { valStrings := []string{} - valSet.Iterate(func(index uint, val *Validator) bool { + valSet.Iterate(func(index int, val *Validator) bool { valStrings = append(valStrings, val.String()) return false }) @@ -294,9 +294,9 @@ func (vs ValidatorsByAddress) Swap(i, j int) { //------------------------------------- // Use with Heap for sorting validators by accum -type accumComparable uint64 +type accumComparable int64 // We want to find the validator with the greatest accum. func (ac accumComparable) Less(o interface{}) bool { - return uint64(ac) < uint64(o.(accumComparable)) + return int64(ac) < int64(o.(accumComparable)) } diff --git a/state/validator_set_test.go b/state/validator_set_test.go index be108acf..dc74d484 100644 --- a/state/validator_set_test.go +++ b/state/validator_set_test.go @@ -13,9 +13,9 @@ func randValidator_() *Validator { return &Validator{ Address: RandBytes(20), PubKey: account.PubKeyEd25519(RandBytes(64)), - BondHeight: uint(RandUint32()), - VotingPower: RandUint64(), - Accum: int64(RandUint64()), + BondHeight: RandInt(), + VotingPower: RandInt64(), + Accum: RandInt64(), } } diff --git a/types/block.go b/types/block.go index b149c44e..2fdf9f1d 100644 --- a/types/block.go +++ b/types/block.go @@ -21,7 +21,7 @@ type Block struct { } // Basic validation that doesn't involve state data. -func (b *Block) ValidateBasic(chainID string, lastBlockHeight uint, lastBlockHash []byte, +func (b *Block) ValidateBasic(chainID string, lastBlockHeight int, lastBlockHash []byte, lastBlockParts PartSetHeader, lastBlockTime time.Time) error { if b.ChainID != chainID { return errors.New("Wrong Block.Header.ChainID") @@ -29,7 +29,7 @@ func (b *Block) ValidateBasic(chainID string, lastBlockHeight uint, lastBlockHas if b.Height != lastBlockHeight+1 { return errors.New("Wrong Block.Header.Height") } - if b.NumTxs != uint(len(b.Data.Txs)) { + if b.NumTxs != len(b.Data.Txs) { return errors.New("Wrong Block.Header.NumTxs") } if !bytes.Equal(b.LastBlockHash, lastBlockHash) { @@ -123,10 +123,10 @@ func (b *Block) StringShort() string { type Header struct { ChainID string `json:"chain_id"` - Height uint `json:"height"` + Height int `json:"height"` Time time.Time `json:"time"` - Fees uint64 `json:"fees"` - NumTxs uint `json:"num_txs"` + Fees int64 `json:"fees"` + NumTxs int `json:"num_txs"` LastBlockHash []byte `json:"last_block_hash"` LastBlockParts PartSetHeader `json:"last_block_parts"` StateHash []byte `json:"state_hash"` @@ -188,14 +188,14 @@ type Validation struct { bitArray *BitArray } -func (v *Validation) Height() uint { +func (v *Validation) Height() int { if len(v.Precommits) == 0 { return 0 } return v.Precommits[0].Height } -func (v *Validation) Round() uint { +func (v *Validation) Round() int { if len(v.Precommits) == 0 { return 0 } @@ -259,9 +259,9 @@ func (v *Validation) StringIndented(indent string) string { func (v *Validation) BitArray() *BitArray { if v.bitArray == nil { - v.bitArray = NewBitArray(uint(len(v.Precommits))) + v.bitArray = NewBitArray(len(v.Precommits)) for i, precommit := range v.Precommits { - v.bitArray.SetIndex(uint(i), precommit != nil) + v.bitArray.SetIndex(i, precommit != nil) } } return v.bitArray diff --git a/types/events.go b/types/events.go index e51d9e28..31cedf12 100644 --- a/types/events.go +++ b/types/events.go @@ -55,8 +55,8 @@ type CallData struct { Caller []byte `json:"caller"` Callee []byte `json:"callee"` Data []byte `json:"data"` - Value uint64 `json:"value"` - Gas uint64 `json:"gas"` + Value int64 `json:"value"` + Gas int64 `json:"gas"` } type EventMsgCall struct { diff --git a/types/names.go b/types/names.go index bebecb47..9ce4cc6f 100644 --- a/types/names.go +++ b/types/names.go @@ -5,12 +5,12 @@ import ( ) var ( - MinNameRegistrationPeriod uint64 = 5 + MinNameRegistrationPeriod int = 5 // cost for storing a name for a block is // CostPerBlock*CostPerByte*(len(data) + 32) - NameCostPerByte uint64 = 1 - NameCostPerBlock uint64 = 1 + NameCostPerByte int64 = 1 + NameCostPerBlock int64 = 1 MaxNameLength = 32 MaxDataLength = 1 << 16 @@ -31,15 +31,15 @@ func validateNameRegEntryData(data string) bool { } // base cost is "effective" number of bytes -func BaseEntryCost(name, data string) uint64 { - return uint64(len(data) + 32) +func BaseEntryCost(name, data string) int64 { + return int64(len(data) + 32) } type NameRegEntry struct { Name string `json:"name"` // registered name for the entry Owner []byte `json:"owner"` // address that created the entry Data string `json:"data"` // data to store under this name - Expires uint64 `json:"expires"` // block at which this entry expires + Expires int `json:"expires"` // block at which this entry expires } func (entry *NameRegEntry) Copy() *NameRegEntry { diff --git a/types/part_set.go b/types/part_set.go index 8c5de3be..8d4ba320 100644 --- a/types/part_set.go +++ b/types/part_set.go @@ -61,7 +61,7 @@ func (part *Part) StringIndented(indent string) string { //------------------------------------- type PartSetHeader struct { - Total uint `json:"total"` + Total int `json:"total"` Hash []byte `json:"hash"` } @@ -84,13 +84,13 @@ func (psh PartSetHeader) WriteSignBytes(w io.Writer, n *int64, err *error) { //------------------------------------- type PartSet struct { - total uint + total int hash []byte mtx sync.Mutex parts []*Part partsBitArray *BitArray - count uint + count int } // Returns an immutable, full PartSet from the data bytes. @@ -100,14 +100,14 @@ func NewPartSetFromData(data []byte) *PartSet { total := (len(data) + partSize - 1) / partSize parts := make([]*Part, total) parts_ := make([]merkle.Hashable, total) - partsBitArray := NewBitArray(uint(total)) + partsBitArray := NewBitArray(total) for i := 0; i < total; i++ { part := &Part{ Bytes: data[i*partSize : MinInt(len(data), (i+1)*partSize)], } parts[i] = part parts_[i] = part - partsBitArray.SetIndex(uint(i), true) + partsBitArray.SetIndex(i, true) } // Compute merkle proofs proofs := merkle.SimpleProofsFromHashables(parts_) @@ -115,11 +115,11 @@ func NewPartSetFromData(data []byte) *PartSet { parts[i].Proof = *proofs[i] } return &PartSet{ - total: uint(total), + total: total, hash: proofs[0].RootHash, parts: parts, partsBitArray: partsBitArray, - count: uint(total), + count: total, } } @@ -129,7 +129,7 @@ func NewPartSetFromHeader(header PartSetHeader) *PartSet { total: header.Total, hash: header.Hash, parts: make([]*Part, header.Total), - partsBitArray: NewBitArray(uint(header.Total)), + partsBitArray: NewBitArray(header.Total), count: 0, } } @@ -173,14 +173,14 @@ func (ps *PartSet) HashesTo(hash []byte) bool { return bytes.Equal(ps.hash, hash) } -func (ps *PartSet) Count() uint { +func (ps *PartSet) Count() int { if ps == nil { return 0 } return ps.count } -func (ps *PartSet) Total() uint { +func (ps *PartSet) Total() int { if ps == nil { return 0 } @@ -208,12 +208,12 @@ func (ps *PartSet) AddPart(part *Part) (bool, error) { // Add part ps.parts[part.Proof.Index] = part - ps.partsBitArray.SetIndex(uint(part.Proof.Index), true) + ps.partsBitArray.SetIndex(part.Proof.Index, true) ps.count++ return true, nil } -func (ps *PartSet) GetPart(index uint) *Part { +func (ps *PartSet) GetPart(index int) *Part { ps.mtx.Lock() defer ps.mtx.Unlock() return ps.parts[index] diff --git a/types/part_set_test.go b/types/part_set_test.go index 88cf2d58..5d2d9142 100644 --- a/types/part_set_test.go +++ b/types/part_set_test.go @@ -27,7 +27,7 @@ func TestBasicPartSet(t *testing.T) { // Test adding parts to a new partSet. partSet2 := NewPartSetFromHeader(partSet.Header()) - for i := uint(0); i < partSet.Total(); i++ { + for i := 0; i < partSet.Total(); i++ { part := partSet.GetPart(i) //t.Logf("\n%v", part) added, err := partSet2.AddPart(part) diff --git a/types/tx.go b/types/tx.go index 46362413..690528bc 100644 --- a/types/tx.go +++ b/types/tx.go @@ -24,8 +24,8 @@ var ( ) type ErrTxInvalidSequence struct { - Got uint64 - Expected uint64 + Got int + Expected int } func (e ErrTxInvalidSequence) Error() string { @@ -79,8 +79,8 @@ var _ = binary.RegisterInterface( type TxInput struct { Address []byte `json:"address"` // Hash of the PubKey - Amount uint64 `json:"amount"` // Must not exceed account balance - Sequence uint `json:"sequence"` // Must be 1 greater than the last committed TxInput + Amount int64 `json:"amount"` // Must not exceed account balance + Sequence int `json:"sequence"` // Must be 1 greater than the last committed TxInput Signature account.Signature `json:"signature"` // Depends on the PubKey type and the whole Tx PubKey account.PubKey `json:"pub_key"` // Must not be nil, may be nil } @@ -107,7 +107,7 @@ func (txIn *TxInput) String() string { type TxOutput struct { Address []byte `json:"address"` // Hash of the PubKey - Amount uint64 `json:"amount"` // The sum of all outputs must not exceed the inputs. + Amount int64 `json:"amount"` // The sum of all outputs must not exceed the inputs. } func (txOut *TxOutput) ValidateBasic() error { @@ -163,8 +163,8 @@ func (tx *SendTx) String() string { type CallTx struct { Input *TxInput `json:"input"` Address []byte `json:"address"` - GasLimit uint64 `json:"gas_limit"` - Fee uint64 `json:"fee"` + GasLimit int64 `json:"gas_limit"` + Fee int64 `json:"fee"` Data []byte `json:"data"` } @@ -186,7 +186,7 @@ type NameTx struct { Input *TxInput `json:"input"` Name string `json:"name"` Data string `json:"data"` - Fee uint64 `json:"fee"` + Fee int64 `json:"fee"` } func (tx *NameTx) WriteSignBytes(chainID string, w io.Writer, n *int64, err *error) { @@ -220,7 +220,7 @@ func (tx *NameTx) ValidateStrings() error { return nil } -func (tx *NameTx) BaseEntryCost() uint64 { +func (tx *NameTx) BaseEntryCost() int64 { return BaseEntryCost(tx.Name, tx.Data) } @@ -266,7 +266,7 @@ func (tx *BondTx) String() string { type UnbondTx struct { Address []byte `json:"address"` - Height uint `json:"height"` + Height int `json:"height"` Signature account.SignatureEd25519 `json:"signature"` } @@ -283,7 +283,7 @@ func (tx *UnbondTx) String() string { type RebondTx struct { Address []byte `json:"address"` - Height uint `json:"height"` + Height int `json:"height"` Signature account.SignatureEd25519 `json:"signature"` } diff --git a/types/tx_utils.go b/types/tx_utils.go index a4ba02de..1a960b6a 100644 --- a/types/tx_utils.go +++ b/types/tx_utils.go @@ -19,7 +19,7 @@ func NewSendTx() *SendTx { } } -func (tx *SendTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) error { +func (tx *SendTx) AddInput(st AccountGetter, pubkey account.PubKey, amt int64) error { addr := pubkey.Address() acc := st.GetAccount(addr) if acc == nil { @@ -28,7 +28,7 @@ func (tx *SendTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) } -func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uint) error { +func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt int64, nonce int) error { addr := pubkey.Address() tx.Inputs = append(tx.Inputs, &TxInput{ Address: addr, @@ -40,7 +40,7 @@ func (tx *SendTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uin return nil } -func (tx *SendTx) AddOutput(addr []byte, amt uint64) error { +func (tx *SendTx) AddOutput(addr []byte, amt int64) error { tx.Outputs = append(tx.Outputs, &TxOutput{ Address: addr, Amount: amt, @@ -60,7 +60,7 @@ func (tx *SendTx) SignInput(chainID string, i int, privAccount *account.PrivAcco //---------------------------------------------------------------------------- // CallTx interface for creating tx -func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasLimit, fee uint64) (*CallTx, error) { +func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasLimit, fee int64) (*CallTx, error) { addr := from.Address() acc := st.GetAccount(addr) if acc == nil { @@ -71,7 +71,7 @@ func NewCallTx(st AccountGetter, from account.PubKey, to, data []byte, amt, gasL return NewCallTxWithNonce(from, to, data, amt, gasLimit, fee, nonce), nil } -func NewCallTxWithNonce(from account.PubKey, to, data []byte, amt, gasLimit, fee uint64, nonce uint) *CallTx { +func NewCallTxWithNonce(from account.PubKey, to, data []byte, amt, gasLimit, fee int64, nonce int) *CallTx { addr := from.Address() input := &TxInput{ Address: addr, @@ -98,7 +98,7 @@ func (tx *CallTx) Sign(chainID string, privAccount *account.PrivAccount) { //---------------------------------------------------------------------------- // NameTx interface for creating tx -func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fee uint64) (*NameTx, error) { +func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fee int64) (*NameTx, error) { addr := from.Address() acc := st.GetAccount(addr) if acc == nil { @@ -109,7 +109,7 @@ func NewNameTx(st AccountGetter, from account.PubKey, name, data string, amt, fe return NewNameTxWithNonce(from, name, data, amt, fee, nonce), nil } -func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee uint64, nonce uint) *NameTx { +func NewNameTxWithNonce(from account.PubKey, name, data string, amt, fee int64, nonce int) *NameTx { addr := from.Address() input := &TxInput{ Address: addr, @@ -147,7 +147,7 @@ func NewBondTx(pubkey account.PubKey) (*BondTx, error) { }, nil } -func (tx *BondTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) error { +func (tx *BondTx) AddInput(st AccountGetter, pubkey account.PubKey, amt int64) error { addr := pubkey.Address() acc := st.GetAccount(addr) if acc == nil { @@ -156,7 +156,7 @@ func (tx *BondTx) AddInput(st AccountGetter, pubkey account.PubKey, amt uint64) return tx.AddInputWithNonce(pubkey, amt, acc.Sequence+1) } -func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uint) error { +func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt int64, nonce int) error { addr := pubkey.Address() tx.Inputs = append(tx.Inputs, &TxInput{ Address: addr, @@ -168,7 +168,7 @@ func (tx *BondTx) AddInputWithNonce(pubkey account.PubKey, amt uint64, nonce uin return nil } -func (tx *BondTx) AddOutput(addr []byte, amt uint64) error { +func (tx *BondTx) AddOutput(addr []byte, amt int64) error { tx.UnbondTo = append(tx.UnbondTo, &TxOutput{ Address: addr, Amount: amt, @@ -198,7 +198,7 @@ func (tx *BondTx) SignInput(chainID string, i int, privAccount *account.PrivAcco //---------------------------------------------------------------------- // UnbondTx interface for creating tx -func NewUnbondTx(addr []byte, height uint) *UnbondTx { +func NewUnbondTx(addr []byte, height int) *UnbondTx { return &UnbondTx{ Address: addr, Height: height, @@ -212,7 +212,7 @@ func (tx *UnbondTx) Sign(chainID string, privAccount *account.PrivAccount) { //---------------------------------------------------------------------- // RebondTx interface for creating tx -func NewRebondTx(addr []byte, height uint) *RebondTx { +func NewRebondTx(addr []byte, height int) *RebondTx { return &RebondTx{ Address: addr, Height: height, diff --git a/types/vote.go b/types/vote.go index 2e931c23..30205d88 100644 --- a/types/vote.go +++ b/types/vote.go @@ -28,8 +28,8 @@ func (err *ErrVoteConflictingSignature) Error() string { // Represents a prevote, precommit, or commit vote from validators for consensus. type Vote struct { - Height uint `json:"height"` - Round uint `json:"round"` + Height int `json:"height"` + Round int `json:"round"` Type byte `json:"type"` BlockHash []byte `json:"block_hash"` // empty if vote is nil. BlockParts PartSetHeader `json:"block_parts"` // zero if vote is nil. diff --git a/vm/gas.go b/vm/gas.go index ebe5573a..dac978f1 100644 --- a/vm/gas.go +++ b/vm/gas.go @@ -1,17 +1,17 @@ package vm const ( - GasSha3 uint64 = 1 - GasGetAccount uint64 = 1 - GasStorageUpdate uint64 = 1 + GasSha3 int64 = 1 + GasGetAccount int64 = 1 + GasStorageUpdate int64 = 1 - GasStackOp uint64 = 1 + GasStackOp int64 = 1 - GasEcRecover uint64 = 1 - GasSha256Word uint64 = 1 - GasSha256Base uint64 = 1 - GasRipemd160Word uint64 = 1 - GasRipemd160Base uint64 = 1 - GasIdentityWord uint64 = 1 - GasIdentityBase uint64 = 1 + GasEcRecover int64 = 1 + GasSha256Word int64 = 1 + GasSha256Base int64 = 1 + GasRipemd160Word int64 = 1 + GasRipemd160Base int64 = 1 + GasIdentityWord int64 = 1 + GasIdentityBase int64 = 1 ) diff --git a/vm/native.go b/vm/native.go index 8aff146d..b7339bc3 100644 --- a/vm/native.go +++ b/vm/native.go @@ -11,17 +11,17 @@ import ( var nativeContracts = make(map[Word256]NativeContract) func init() { - nativeContracts[Uint64ToWord256(1)] = ecrecoverFunc - nativeContracts[Uint64ToWord256(2)] = sha256Func - nativeContracts[Uint64ToWord256(3)] = ripemd160Func - nativeContracts[Uint64ToWord256(4)] = identityFunc + nativeContracts[Int64ToWord256(1)] = ecrecoverFunc + nativeContracts[Int64ToWord256(2)] = sha256Func + nativeContracts[Int64ToWord256(3)] = ripemd160Func + nativeContracts[Int64ToWord256(4)] = identityFunc } //----------------------------------------------------------------------------- -type NativeContract func(input []byte, gas *uint64) (output []byte, err error) +type NativeContract func(input []byte, gas *int64) (output []byte, err error) -func ecrecoverFunc(input []byte, gas *uint64) (output []byte, err error) { +func ecrecoverFunc(input []byte, gas *int64) (output []byte, err error) { // Deduct gas gasRequired := GasEcRecover if *gas < gasRequired { @@ -42,9 +42,9 @@ func ecrecoverFunc(input []byte, gas *uint64) (output []byte, err error) { return LeftPadBytes(hashed, 32), nil } -func sha256Func(input []byte, gas *uint64) (output []byte, err error) { +func sha256Func(input []byte, gas *int64) (output []byte, err error) { // Deduct gas - gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base + gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -59,9 +59,9 @@ func sha256Func(input []byte, gas *uint64) (output []byte, err error) { return hasher.Sum(nil), nil } -func ripemd160Func(input []byte, gas *uint64) (output []byte, err error) { +func ripemd160Func(input []byte, gas *int64) (output []byte, err error) { // Deduct gas - gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base + gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -76,9 +76,9 @@ func ripemd160Func(input []byte, gas *uint64) (output []byte, err error) { return LeftPadBytes(hasher.Sum(nil), 32), nil } -func identityFunc(input []byte, gas *uint64) (output []byte, err error) { +func identityFunc(input []byte, gas *int64) (output []byte, err error) { // Deduct gas - gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase + gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase if *gas < gasRequired { return nil, ErrInsufficientGas } else { diff --git a/vm/stack.go b/vm/stack.go index 500ff4d1..12bc1b77 100644 --- a/vm/stack.go +++ b/vm/stack.go @@ -10,11 +10,11 @@ type Stack struct { data []Word256 ptr int - gas *uint64 + gas *int64 err *error } -func NewStack(capacity int, gas *uint64, err *error) *Stack { +func NewStack(capacity int, gas *int64, err *error) *Stack { return &Stack{ data: make([]Word256, capacity), ptr: 0, @@ -23,7 +23,7 @@ func NewStack(capacity int, gas *uint64, err *error) *Stack { } } -func (st *Stack) useGas(gasToUse uint64) { +func (st *Stack) useGas(gasToUse int64) { if *st.gas > gasToUse { *st.gas -= gasToUse } else { @@ -54,8 +54,8 @@ func (st *Stack) PushBytes(bz []byte) { st.Push(LeftPadWord256(bz)) } -func (st *Stack) Push64(i uint64) { - st.Push(Uint64ToWord256(i)) +func (st *Stack) Push64(i int64) { + st.Push(Int64ToWord256(i)) } func (st *Stack) Pop() Word256 { @@ -72,9 +72,9 @@ func (st *Stack) PopBytes() []byte { return st.Pop().Bytes() } -func (st *Stack) Pop64() uint64 { +func (st *Stack) Pop64() int64 { d := st.Pop() - return Uint64FromWord256(d) + return Int64FromWord256(d) } func (st *Stack) Len() int { diff --git a/vm/test/fake_app_state.go b/vm/test/fake_app_state.go index 6f703d72..4880814b 100644 --- a/vm/test/fake_app_state.go +++ b/vm/test/fake_app_state.go @@ -80,6 +80,6 @@ func createAddress(creator *Account) Word256 { creator.Nonce += 1 temp := make([]byte, 32+8) copy(temp, creator.Address[:]) - PutUint64BE(temp[32:], nonce) + PutInt64BE(temp[32:], nonce) return LeftPadWord256(sha3.Sha3(temp)[:20]) } diff --git a/vm/test/vm_test.go b/vm/test/vm_test.go index e285a911..716643c4 100644 --- a/vm/test/vm_test.go +++ b/vm/test/vm_test.go @@ -43,13 +43,13 @@ func TestVM(t *testing.T) { // Create accounts account1 := &Account{ - Address: Uint64ToWord256(100), + Address: Int64ToWord256(100), } account2 := &Account{ - Address: Uint64ToWord256(101), + Address: Int64ToWord256(101), } - var gas uint64 = 100000 + var gas int64 = 100000 N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} @@ -81,7 +81,7 @@ func TestSubcurrency(t *testing.T) { ourVm := NewVM(st, newParams(), Zero256, nil) - var gas uint64 = 1000 + var gas int64 = 1000 code_parts := []string{"620f42403355", "7c0100000000000000000000000000000000000000000000000000000000", "600035046315cf268481141561004657", @@ -105,13 +105,13 @@ func TestSendCall(t *testing.T) { // Create accounts account1 := &Account{ - Address: Uint64ToWord256(100), + Address: Int64ToWord256(100), } account2 := &Account{ - Address: Uint64ToWord256(101), + Address: Int64ToWord256(101), } account3 := &Account{ - Address: Uint64ToWord256(102), + Address: Int64ToWord256(102), } // account1 will call account2 which will trigger CALL opcode to account3 @@ -146,7 +146,7 @@ func TestSendCall(t *testing.T) { } // subscribes to an AccReceive, runs the vm, returns the exception -func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas uint64) string { +func runVMWaitEvents(t *testing.T, ourVm *VM, caller, callee *Account, subscribeAddr, contractCode []byte, gas int64) string { // we need to catch the event from the CALL to check for exceptions evsw := new(events.EventSwitch) evsw.Start() diff --git a/vm/types.go b/vm/types.go index d5777213..fe872c83 100644 --- a/vm/types.go +++ b/vm/types.go @@ -10,9 +10,9 @@ const ( type Account struct { Address Word256 - Balance uint64 + Balance int64 Code []byte - Nonce uint64 + Nonce int64 StorageRoot Word256 Other interface{} // For holding all other data. } @@ -26,7 +26,7 @@ type Log struct { Address Word256 Topics []Word256 Data []byte - Height uint64 + Height int64 } type AppState interface { @@ -46,8 +46,8 @@ type AppState interface { } type Params struct { - BlockHeight uint64 + BlockHeight int64 BlockHash Word256 BlockTime int64 - GasLimit uint64 + GasLimit int64 } diff --git a/vm/vm.go b/vm/vm.go index 6a814655..dc58f950 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -72,7 +72,7 @@ func (vm *VM) SetFireable(evc events.Fireable) { // value: To be transferred from caller to callee. Refunded upon error. // gas: Available gas. No refunds for gas. // code: May be nil, since the CALL opcode may be used to send value from contracts to accounts -func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { exception := new(string) defer func() { @@ -109,14 +109,14 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value uint64, ga } // Just like Call() but does not transfer 'value' or modify the callDepth. -func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { +func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) var ( - pc uint64 = 0 - stack = NewStack(dataStackCapacity, gas, &err) - memory = make([]byte, memoryCapacity) - ok = false // convenience + pc int64 = 0 + stack = NewStack(dataStackCapacity, gas, &err) + memory = make([]byte, memoryCapacity) + ok = false // convenience ) for { @@ -388,7 +388,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga if idx < 32 { res = val[idx] } - stack.Push64(uint64(res)) + stack.Push64(int64(res)) dbg.Printf(" => 0x%X\n", res) case SHA3: // 0x20 @@ -444,7 +444,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga dbg.Printf(" => 0x%X\n", res) case CALLDATASIZE: // 0x36 - stack.Push64(uint64(len(input))) + stack.Push64(int64(len(input))) dbg.Printf(" => %d\n", len(input)) case CALLDATACOPY: // 0x37 @@ -463,7 +463,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 - l := uint64(len(code)) + l := int64(len(code)) stack.Push64(l) dbg.Printf(" => %d\n", l) @@ -496,7 +496,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga return nil, firstErr(err, ErrUnknownAddress) } code := acc.Code - l := uint64(len(code)) + l := int64(len(code)) stack.Push64(l) dbg.Printf(" => %d\n", l) @@ -534,11 +534,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga case TIMESTAMP: // 0x42 time := vm.params.BlockTime - stack.Push64(uint64(time)) + stack.Push64(int64(time)) dbg.Printf(" => 0x%X\n", time) case BLOCKHEIGHT: // 0x43 - number := uint64(vm.params.BlockHeight) + number := int64(vm.params.BlockHeight) stack.Push64(number) dbg.Printf(" => 0x%X\n", number) @@ -604,7 +604,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga stack.Push64(pc) case MSIZE: // 0x59 - stack.Push64(uint64(len(memory))) + stack.Push64(int64(len(memory))) case GAS: // 0x5A stack.Push64(*gas) @@ -615,7 +615,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga // Do nothing case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: - a := uint64(op - PUSH1 + 1) + a := int64(op - PUSH1 + 1) codeSegment, ok := subslice(code, pc+1, a) if !ok { return nil, firstErr(err, ErrCodeOutOfBounds) @@ -792,8 +792,8 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value uint64, ga } } -func subslice(data []byte, offset, length uint64) (ret []byte, ok bool) { - size := uint64(len(data)) +func subslice(data []byte, offset, length int64) (ret []byte, ok bool) { + size := int64(len(data)) if size < offset { return nil, false } else if size < offset+length { @@ -812,15 +812,15 @@ func rightMostBytes(data []byte, n int) []byte { return data[offset:] } -func codeGetOp(code []byte, n uint64) OpCode { - if uint64(len(code)) <= n { +func codeGetOp(code []byte, n int64) OpCode { + if int64(len(code)) <= n { return OpCode(0) // stop } else { return OpCode(code[n]) } } -func jump(code []byte, to uint64, pc *uint64) (err error) { +func jump(code []byte, to int64, pc *int64) (err error) { dest := codeGetOp(code, to) if dest != JUMPDEST { dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest) @@ -839,7 +839,7 @@ func firstErr(errA, errB error) error { } } -func useGas(gas *uint64, gasToUse uint64) bool { +func useGas(gas *int64, gasToUse int64) bool { if *gas > gasToUse { *gas -= gasToUse return true @@ -848,7 +848,7 @@ func useGas(gas *uint64, gasToUse uint64) bool { } } -func transfer(from, to *Account, amount uint64) error { +func transfer(from, to *Account, amount int64) error { if from.Balance < amount { return ErrInsufficientBalance } else { From 6781b21d32557ea16f146b9d9c902b3c68ec3692 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 16:43:41 -0700 Subject: [PATCH 19/24] Split ReadBinary into ReadBinary/ReadBinaryPtr. --- binary/binary.go | 15 +++++++++++---- binary/reflect.go | 9 ++++++--- binary/reflect_test.go | 9 +++++---- binary/string.go | 6 +++--- p2p/connection.go | 2 +- 5 files changed, 26 insertions(+), 15 deletions(-) diff --git a/binary/binary.go b/binary/binary.go index 890ac76a..1a565693 100644 --- a/binary/binary.go +++ b/binary/binary.go @@ -9,7 +9,7 @@ import ( func ReadBinary(o interface{}, r io.Reader, n *int64, err *error) interface{} { rv, rt := reflect.ValueOf(o), reflect.TypeOf(o) if rv.Kind() == reflect.Ptr { - readReflectBinary(rv.Elem(), rt.Elem(), Options{}, r, n, err) + readReflectBinary(rv, rt, Options{}, r, n, err) return o } else { ptrRv := reflect.New(rt) @@ -18,12 +18,19 @@ func ReadBinary(o interface{}, r io.Reader, n *int64, err *error) interface{} { } } +func ReadBinaryPtr(o interface{}, r io.Reader, n *int64, err *error) interface{} { + rv, rt := reflect.ValueOf(o), reflect.TypeOf(o) + if rv.Kind() == reflect.Ptr { + readReflectBinary(rv.Elem(), rt.Elem(), Options{}, r, n, err) + return o + } else { + panic("ReadBinaryPtr expects o to be a pointer") + } +} + func WriteBinary(o interface{}, w io.Writer, n *int64, err *error) { rv := reflect.ValueOf(o) rt := reflect.TypeOf(o) - if rv.Kind() == reflect.Ptr { - rv, rt = rv.Elem(), rt.Elem() - } writeReflectBinary(rv, rt, Options{}, w, n, err) } diff --git a/binary/reflect.go b/binary/reflect.go index 6eac5a46..b497cf5f 100644 --- a/binary/reflect.go +++ b/binary/reflect.go @@ -226,6 +226,9 @@ func readReflectBinary(rv reflect.Value, rt reflect.Type, opts Options, r io.Rea typeInfo = GetTypeInfo(rt) if typeInfo.Byte != 0x00 { r = NewPrefixedReader([]byte{typeByte}, r) + } else if typeByte != 0x01 { + *err = errors.New(Fmt("Unexpected type byte %X for ptr of untyped thing", typeByte)) + return } // continue... } @@ -250,7 +253,7 @@ func readReflectBinary(rv reflect.Value, rt reflect.Type, opts Options, r io.Rea } else { var sliceRv reflect.Value // Read length - length := int(ReadUvarint(r, n, err)) + length := ReadVarint(r, n, err) log.Debug(Fmt("Read length: %v", length)) sliceRv = reflect.MakeSlice(rt, 0, 0) // read one ReflectSliceChunk at a time and append @@ -322,7 +325,7 @@ func readReflectBinary(rv reflect.Value, rt reflect.Type, opts Options, r io.Rea case reflect.Uint64: if opts.Varint { - num := ReadUvarint(r, n, err) + num := ReadVarint(r, n, err) log.Debug(Fmt("Read num: %v", num)) rv.SetUint(uint64(num)) } else { @@ -347,7 +350,7 @@ func readReflectBinary(rv reflect.Value, rt reflect.Type, opts Options, r io.Rea rv.SetUint(uint64(num)) case reflect.Uint: - num := ReadUvarint(r, n, err) + num := ReadVarint(r, n, err) log.Debug(Fmt("Read num: %v", num)) rv.SetUint(uint64(num)) diff --git a/binary/reflect_test.go b/binary/reflect_test.go index 3fd3f765..8d1b0386 100644 --- a/binary/reflect_test.go +++ b/binary/reflect_test.go @@ -72,13 +72,14 @@ func TestAnimalInterface(t *testing.T) { ptr := reflect.New(rte).Interface() fmt.Printf("ptr: %v", ptr) - // Make a binary byteslice that represents a snake. - snakeBytes := BinaryBytes(Snake([]byte("snake"))) + // Make a binary byteslice that represents a *snake. + foo = Snake([]byte("snake")) + snakeBytes := BinaryBytes(foo) snakeReader := bytes.NewReader(snakeBytes) // Now you can read it. n, err := new(int64), new(error) - it := *ReadBinary(ptr, snakeReader, n, err).(*Animal) + it := ReadBinary(foo, snakeReader, n, err).(Animal) fmt.Println(it, reflect.TypeOf(it)) } @@ -374,7 +375,7 @@ func TestBinary(t *testing.T) { // Read onto a pointer n, err = new(int64), new(error) - res = ReadBinary(instancePtr, bytes.NewReader(data), n, err) + res = ReadBinaryPtr(instancePtr, bytes.NewReader(data), n, err) if *err != nil { t.Fatalf("Failed to read into instance: %v", *err) } diff --git a/binary/string.go b/binary/string.go index c7ebd7f7..fb0bfc7d 100644 --- a/binary/string.go +++ b/binary/string.go @@ -5,16 +5,16 @@ import "io" // String func WriteString(s string, w io.Writer, n *int64, err *error) { - WriteUvarint(uint(len(s)), w, n, err) + WriteVarint(len(s), w, n, err) WriteTo([]byte(s), w, n, err) } func ReadString(r io.Reader, n *int64, err *error) string { - length := ReadUvarint(r, n, err) + length := ReadVarint(r, n, err) if *err != nil { return "" } - buf := make([]byte, int(length)) + buf := make([]byte, length) ReadFull(buf, r, n, err) return string(buf) } diff --git a/p2p/connection.go b/p2p/connection.go index 856f40f3..a7bda8a9 100644 --- a/p2p/connection.go +++ b/p2p/connection.go @@ -403,7 +403,7 @@ FOR_LOOP: log.Debug("Receive Pong") case packetTypeMsg: pkt, n, err := msgPacket{}, new(int64), new(error) - binary.ReadBinary(&pkt, c.bufReader, n, err) + binary.ReadBinaryPtr(&pkt, c.bufReader, n, err) c.recvMonitor.Update(int(*n)) if *err != nil { if atomic.LoadUint32(&c.stopped) != 1 { From 9b00f329014837c07bfcd30113893c1a07f40352 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 17:07:19 -0700 Subject: [PATCH 20/24] Fix consensus: Send round-skip votes --- binary/README.md | 2 +- consensus/reactor.go | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/binary/README.md b/binary/README.md index c490d0e5..9a4fa714 100644 --- a/binary/README.md +++ b/binary/README.md @@ -78,7 +78,7 @@ WriteBinary(foo, buf, n, err) foo2 := ReadBinary(Foo{}, buf, n, err).(Foo) // Or, to decode onto a pointer: -foo2 := ReadBinary(&Foo{}, buf, n, err).(*Foo) +foo2 := ReadBinaryPtr(&Foo{}, buf, n, err).(*Foo) ``` WriteBinary and ReadBinary can encode/decode structs recursively. However, interface field diff --git a/consensus/reactor.go b/consensus/reactor.go index 56a873d3..0218f77c 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -520,6 +520,18 @@ OUTER_LOOP: continue OUTER_LOOP } } + // If there are prevotes to send for the last round... + if rs.Round == prs.Round+1 && prs.Step <= RoundStepPrevote { + if trySendVote(rs.Votes.Prevotes(prs.Round), &prs.Prevotes) { + continue OUTER_LOOP + } + } + // If there are precommits to send for the last round... + if rs.Round == prs.Round+1 && prs.Step <= RoundStepPrecommit { + if trySendVote(rs.Votes.Precommits(prs.Round), &prs.Precommits) { + continue OUTER_LOOP + } + } // If there are POLPrevotes to send... if 0 <= prs.ProposalPOLRound { if polPrevotes := rs.Votes.Prevotes(prs.ProposalPOLRound); polPrevotes != nil { From 24b9f8647c5660372aae6698deab2337defa7633 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 17:14:40 -0700 Subject: [PATCH 21/24] fix debug output --- consensus/reactor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index 0218f77c..2032c943 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -938,7 +938,7 @@ type BlockPartMessage struct { } func (m *BlockPartMessage) String() string { - return fmt.Sprintf("[BlockPart H:%v R:%v T:%X P:%v]", m.Height, m.Round, m.Part) + return fmt.Sprintf("[BlockPart H:%v R:%v P:%v]", m.Height, m.Round, m.Part) } //------------------------------------- From 4d209ee3491845320c2387473002bf805c30f5c8 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 17:25:47 -0700 Subject: [PATCH 22/24] Fix consensus: use the right ValidatorSet upon Vote receive --- consensus/reactor.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index 2032c943..236f8e8f 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -177,16 +177,19 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte switch msg := msg_.(type) { case *VoteMessage: vote := msg.Vote - if rs.Height != vote.Height { - if rs.Height == vote.Height+1 { - if rs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit { - goto VOTE_PASS // *ducks* - } + var validators *ValidatorSet + if rs.Height == vote.Height { + validators = rs.Validators + } else if rs.Height == vote.Height+1 { + validators = rs.LastBondedValidators + if !(rs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { + return // Wrong height, not a LastCommit straggler commit. } + } else { return // Wrong height. Not necessarily a bad peer. } VOTE_PASS: - address, _ := rs.Validators.GetByIndex(msg.ValidatorIndex) + address, _ := validators.GetByIndex(msg.ValidatorIndex) added, index, err := conR.conS.AddVote(address, vote, peer.Key) if err != nil { // If conflicting sig, broadcast evidence tx for slashing. Else punish peer. From 26c192b047f304591078fd23e1b11855794aa94f Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 17:48:24 -0700 Subject: [PATCH 23/24] Fix consensus: * Use LastValidators when a Vote is received for the last height. * Fix Validation.Height/Round to use FirstPrecommit. --- consensus/reactor.go | 8 +++++--- consensus/state.go | 4 ++++ types/block.go | 25 +++++++++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/consensus/reactor.go b/consensus/reactor.go index 236f8e8f..971a677b 100644 --- a/consensus/reactor.go +++ b/consensus/reactor.go @@ -177,18 +177,20 @@ func (conR *ConsensusReactor) Receive(chId byte, peer *p2p.Peer, msgBytes []byte switch msg := msg_.(type) { case *VoteMessage: vote := msg.Vote - var validators *ValidatorSet + var validators *sm.ValidatorSet if rs.Height == vote.Height { validators = rs.Validators } else if rs.Height == vote.Height+1 { - validators = rs.LastBondedValidators if !(rs.Step == RoundStepNewHeight && vote.Type == types.VoteTypePrecommit) { return // Wrong height, not a LastCommit straggler commit. } + validators = rs.LastValidators } else { return // Wrong height. Not necessarily a bad peer. } - VOTE_PASS: + + // We have vote/validators. Height may not be rs.Height + address, _ := validators.GetByIndex(msg.ValidatorIndex) added, index, err := conR.conS.AddVote(address, vote, peer.Key) if err != nil { diff --git a/consensus/state.go b/consensus/state.go index b74e9a0d..1c6820f3 100644 --- a/consensus/state.go +++ b/consensus/state.go @@ -215,6 +215,7 @@ type RoundState struct { LockedBlockParts *types.PartSet Votes *HeightVoteSet LastCommit *VoteSet // Last precommits at Height-1 + LastValidators *sm.ValidatorSet } func (rs *RoundState) String() string { @@ -233,6 +234,7 @@ func (rs *RoundState) StringIndented(indent string) string { %s LockedBlock: %v %v %s Votes: %v %s LastCommit: %v +%s LastValidators: %v %s}`, indent, rs.Height, rs.Round, rs.Step, indent, rs.StartTime, @@ -244,6 +246,7 @@ func (rs *RoundState) StringIndented(indent string) string { indent, rs.LockedBlockParts.StringShort(), rs.LockedBlock.StringShort(), indent, rs.Votes.StringIndented(indent+" "), indent, rs.LastCommit.StringShort(), + indent, rs.LastValidators.StringIndented(indent+" "), indent) } @@ -406,6 +409,7 @@ func (cs *ConsensusState) updateToState(state *sm.State, contiguous bool) { cs.LockedBlockParts = nil cs.Votes = NewHeightVoteSet(height, validators) cs.LastCommit = lastPrecommits + cs.LastValidators = state.LastBondedValidators cs.state = state cs.stagedBlock = nil diff --git a/types/block.go b/types/block.go index 2fdf9f1d..6f004a93 100644 --- a/types/block.go +++ b/types/block.go @@ -184,22 +184,39 @@ type Validation struct { Precommits []*Vote `json:"precommits"` // Volatile - hash []byte - bitArray *BitArray + firstPrecommit *Vote + hash []byte + bitArray *BitArray +} + +func (v *Validation) FirstPrecommit() *Vote { + if len(v.Precommits) == 0 { + return nil + } + if v.firstPrecommit != nil { + return v.firstPrecommit + } + for _, precommit := range v.Precommits { + if precommit != nil { + v.firstPrecommit = precommit + return precommit + } + } + return nil } func (v *Validation) Height() int { if len(v.Precommits) == 0 { return 0 } - return v.Precommits[0].Height + return v.FirstPrecommit().Height } func (v *Validation) Round() int { if len(v.Precommits) == 0 { return 0 } - return v.Precommits[0].Round + return v.FirstPrecommit().Round } func (v *Validation) ValidateBasic() error { From 027ad79f9c224d15f102500f7036adf27164a5a3 Mon Sep 17 00:00:00 2001 From: Jae Kwon Date: Fri, 26 Jun 2015 17:54:02 -0700 Subject: [PATCH 24/24] Fix String() for nil votes, and non-full Validation --- types/vote.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/types/vote.go b/types/vote.go index 30205d88..4fca422d 100644 --- a/types/vote.go +++ b/types/vote.go @@ -54,6 +54,9 @@ func (vote *Vote) Copy() *Vote { } func (vote *Vote) String() string { + if vote == nil { + return "nil-Vote" + } var typeString string switch vote.Type { case VoteTypePrevote: