Browse Source

consensus cleanup, privValidator config

pull/9/head
Jae Kwon 10 years ago
parent
commit
647d26f7a0
11 changed files with 197 additions and 121 deletions
  1. +3
    -3
      consensus/priv_validator.go
  2. +30
    -54
      consensus/reactor.go
  3. +51
    -24
      consensus/state.go
  4. +29
    -37
      consensus/state_test.go
  5. +10
    -0
      consensus/vote_set.go
  6. +31
    -0
      gen_account.go
  7. +1
    -1
      p2p/switch.go
  8. +20
    -0
      state/account.go
  9. +2
    -2
      state/genesis.go
  10. +5
    -0
      state/validator_set.go
  11. +15
    -0
      tendermintd.go

+ 3
- 3
consensus/priv_validator.go View File

@ -9,12 +9,12 @@ import (
//-----------------------------------------------------------------------------
type PrivValidator struct {
state.PrivAccount
db db_.DB
state.PrivAccount
}
func NewPrivValidator(priv *state.PrivAccount, db db_.DB) *PrivValidator {
return &PrivValidator{*priv, db}
func NewPrivValidator(db db_.DB, priv *state.PrivAccount) *PrivValidator {
return &PrivValidator{db, *priv}
}
// Double signing results in a panic.


+ 30
- 54
consensus/reactor.go View File

@ -58,14 +58,14 @@ func calcRound(startTime time.Time) uint16 {
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.
// 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)))
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")
}
@ -300,20 +300,20 @@ func (conR *ConsensusReactor) stepTransitionRoutine() {
// It's a new RoundState.
if elapsedRatio < 0 {
// startTime is in the future.
time.Sleep(time.Duration(-1.0*elapsedRatio) * roundDuration)
time.Sleep(time.Duration((-1.0 * elapsedRatio) * float64(roundDuration)))
}
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPropose}
case RoundStepPropose:
// Wake up when it's time to vote.
time.Sleep(time.Duration(roundDeadlinePrevote-elapsedRatio) * roundDuration)
time.Sleep(time.Duration((roundDeadlinePrevote - elapsedRatio) * float64(roundDuration)))
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrevote}
case RoundStepPrevote:
// Wake up when it's time to precommit.
time.Sleep(time.Duration(roundDeadlinePrecommit-elapsedRatio) * roundDuration)
time.Sleep(time.Duration((roundDeadlinePrecommit - elapsedRatio) * float64(roundDuration)))
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionPrecommit}
case RoundStepPrecommit:
// Wake up when the round is over.
time.Sleep(time.Duration(1.0-elapsedRatio) * roundDuration)
time.Sleep(time.Duration((1.0 - elapsedRatio) * float64(roundDuration)))
conR.doActionCh <- RoundAction{rs.Height, rs.Round, RoundActionNextRound}
case RoundStepCommit:
panic("Should not happen: RoundStepCommit waits until +2/3 commits.")
@ -344,6 +344,8 @@ ACTION_LOOP:
round := roundAction.Round
action := roundAction.Action
rs := conR.conS.GetRoundState()
log.Info("Running round action A:%X %v", action, rs.Description())
broadcastNewRoundStep := func(step RoundStep) {
// Broadcast NewRoundStepMessage
msg := &NewRoundStepMessage{
@ -379,14 +381,11 @@ ACTION_LOOP:
if rs.Step >= RoundStepPrevote {
continue ACTION_LOOP
}
hash := conR.conS.RunActionPrevote(rs.Height, rs.Round)
vote := conR.conS.RunActionPrevote(rs.Height, rs.Round)
broadcastNewRoundStep(RoundStepPrevote)
conR.signAndBroadcastVote(rs, &Vote{
Height: rs.Height,
Round: rs.Round,
Type: VoteTypePrevote,
BlockHash: hash,
})
if vote != nil {
conR.broadcastVote(vote)
}
scheduleNextAction()
continue ACTION_LOOP
@ -394,15 +393,10 @@ ACTION_LOOP:
if rs.Step >= RoundStepPrecommit {
continue ACTION_LOOP
}
hash := conR.conS.RunActionPrecommit(rs.Height, rs.Round)
vote := conR.conS.RunActionPrecommit(rs.Height, rs.Round)
broadcastNewRoundStep(RoundStepPrecommit)
if len(hash) > 0 {
conR.signAndBroadcastVote(rs, &Vote{
Height: rs.Height,
Round: rs.Round,
Type: VoteTypePrecommit,
BlockHash: hash,
})
if vote != nil {
conR.broadcastVote(vote)
}
scheduleNextAction()
continue ACTION_LOOP
@ -420,17 +414,10 @@ ACTION_LOOP:
continue ACTION_LOOP
}
// NOTE: Duplicated in RoundActionCommitWait.
hash := conR.conS.RunActionCommit(rs.Height)
if len(hash) > 0 {
broadcastNewRoundStep(RoundStepCommit)
conR.signAndBroadcastVote(rs, &Vote{
Height: rs.Height,
Round: rs.Round,
Type: VoteTypeCommit,
BlockHash: hash,
})
} else {
panic("This shouldn't happen")
vote := conR.conS.RunActionCommit(rs.Height, rs.Round)
broadcastNewRoundStep(RoundStepCommit)
if vote != nil {
conR.broadcastVote(vote)
}
// do not schedule next action.
continue ACTION_LOOP
@ -442,21 +429,14 @@ ACTION_LOOP:
// Commit first we haven't already.
if rs.Step < RoundStepCommit {
// NOTE: Duplicated in RoundActionCommit.
hash := conR.conS.RunActionCommit(rs.Height)
if len(hash) > 0 {
broadcastNewRoundStep(RoundStepCommit)
conR.signAndBroadcastVote(rs, &Vote{
Height: rs.Height,
Round: rs.Round,
Type: VoteTypeCommit,
BlockHash: hash,
})
} else {
panic("This shouldn't happen")
vote := conR.conS.RunActionCommit(rs.Height, rs.Round)
broadcastNewRoundStep(RoundStepCommit)
if vote != nil {
conR.broadcastVote(vote)
}
}
// Wait for more commit votes.
conR.conS.RunActionCommitWait(rs.Height)
conR.conS.RunActionCommitWait(rs.Height, rs.Round)
scheduleNextAction()
continue ACTION_LOOP
@ -464,7 +444,7 @@ ACTION_LOOP:
if rs.Step != RoundStepCommitWait {
panic("This shouldn't happen")
}
conR.conS.RunActionFinalize(rs.Height)
conR.conS.RunActionFinalize(rs.Height, rs.Round)
// Height has been incremented, step is now RoundStepStart.
scheduleNextAction()
continue ACTION_LOOP
@ -478,13 +458,9 @@ ACTION_LOOP:
}
}
func (conR *ConsensusReactor) signAndBroadcastVote(rs *RoundState, vote *Vote) {
if rs.PrivValidator != nil {
rs.PrivValidator.Sign(vote)
conR.conS.AddVote(vote)
msg := p2p.TypedMessage{msgTypeVote, vote}
conR.sw.Broadcast(VoteCh, msg)
}
func (conR *ConsensusReactor) broadcastVote(vote *Vote) {
msg := p2p.TypedMessage{msgTypeVote, vote}
conR.sw.Broadcast(VoteCh, msg)
}
//--------------------------------------
@ -826,7 +802,7 @@ func (m *NewRoundStepMessage) WriteTo(w io.Writer) (n int64, err error) {
}
func (m *NewRoundStepMessage) String() string {
return fmt.Sprintf("[NewRoundStepMessage H:%v R:%v]", m.Height, m.Round)
return fmt.Sprintf("[NewRoundStep %v/%v/%X]", m.Height, m.Round, m.Step)
}
//-------------------------------------


+ 51
- 24
consensus/state.go View File

@ -22,8 +22,8 @@ const (
RoundStepPropose = RoundStep(0x01) // Did propose, gossip proposal.
RoundStepPrevote = RoundStep(0x02) // Did prevote, gossip prevotes.
RoundStepPrecommit = RoundStep(0x03) // Did precommit, gossip precommits.
RoundStepCommit = RoundStep(0x04) // Did commit, gossip commits.
RoundStepCommitWait = RoundStep(0x05) // Found +2/3 commits, wait more.
RoundStepCommit = RoundStep(0x10) // Did commit, gossip commits.
RoundStepCommitWait = RoundStep(0x11) // Found +2/3 commits, wait more.
// If a block could not be committed at a given round,
// we progress to the next round, skipping RoundStepCommit.
@ -37,9 +37,9 @@ const (
RoundActionPrevote = RoundActionType(0x01) // Goto RoundStepPrevote
RoundActionPrecommit = RoundActionType(0x02) // Goto RoundStepPrecommit
RoundActionNextRound = RoundActionType(0x04) // Goto next round RoundStepStart
RoundActionCommit = RoundActionType(0x05) // Goto RoundStepCommit or RoundStepStart next round
RoundActionCommitWait = RoundActionType(0x06) // Goto RoundStepCommitWait
RoundActionFinalize = RoundActionType(0x07) // Goto RoundStepStart next height
RoundActionCommit = RoundActionType(0x10) // Goto RoundStepCommit or RoundStepStart next round
RoundActionCommitWait = RoundActionType(0x11) // Goto RoundStepCommitWait
RoundActionFinalize = RoundActionType(0x12) // Goto RoundStepStart next height
)
var (
@ -107,10 +107,15 @@ func (rs *RoundState) StringWithIndent(indent string) string {
indent, rs.Prevotes.StringWithIndent(indent+" "),
indent, rs.Precommits.StringWithIndent(indent+" "),
indent, rs.Commits.StringWithIndent(indent+" "),
indent, rs.LastCommits.StringWithIndent(indent+" "),
indent, rs.LastCommits.Description(),
indent)
}
func (rs *RoundState) Description() string {
return fmt.Sprintf(`RS{%v/%v/%X %v}`,
rs.Height, rs.Round, rs.Step, rs.StartTime)
}
//-------------------------------------
// Tracks consensus state across block heights and rounds.
@ -298,17 +303,21 @@ func (cs *ConsensusState) RunActionPropose(height uint32, round uint16) {
cs.ProposalPOLPartSet = polPartSet
}
func (cs *ConsensusState) RunActionPrevote(height uint32, round uint16) []byte {
func (cs *ConsensusState) RunActionPrevote(height uint32, round uint16) *Vote {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round != round {
return nil
Panicf("RunActionPrevote(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)
}
cs.Step = RoundStepPrevote
// If a block is locked, prevote that.
if cs.LockedBlock != nil {
return cs.LockedBlock.Hash()
return cs.signAddVote(VoteTypePrevote, cs.LockedBlock.Hash())
}
// If ProposalBlock is nil, prevote nil.
if cs.ProposalBlock == nil {
return nil
}
// Try staging proposed block.
err := cs.stageBlock(cs.ProposalBlock)
@ -317,18 +326,18 @@ func (cs *ConsensusState) RunActionPrevote(height uint32, round uint16) []byte {
return nil
} else {
// Prevote block.
return cs.ProposalBlock.Hash()
return cs.signAddVote(VoteTypePrevote, cs.ProposalBlock.Hash())
}
}
// Lock the ProposalBlock if we have enough prevotes for it,
// or unlock an existing lock if +2/3 of prevotes were nil.
// Returns a blockhash if a block was locked.
func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte {
func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) *Vote {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height || cs.Round != round {
return nil
Panicf("RunActionPrecommit(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)
}
cs.Step = RoundStepPrecommit
@ -352,10 +361,10 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte
}
cs.LockedBlock = cs.ProposalBlock
cs.LockedBlockPartSet = cs.ProposalBlockPartSet
return hash
return cs.signAddVote(VoteTypePrecommit, hash)
} else if cs.LockedBlock.HashesTo(hash) {
// +2/3 prevoted for already locked block
return hash
return cs.signAddVote(VoteTypePrecommit, hash)
} else {
// We don't have the block that hashes to hash.
// Unlock if we're locked.
@ -373,11 +382,11 @@ func (cs *ConsensusState) RunActionPrecommit(height uint32, round uint16) []byte
// and returns the committed block.
// Commit is not finalized until FinalizeCommit() is called.
// This allows us to stay at this height and gather more commits.
func (cs *ConsensusState) RunActionCommit(height uint32) []byte {
func (cs *ConsensusState) RunActionCommit(height uint32, round uint16) *Vote {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height {
return nil
if cs.Height != height || cs.Round != round {
Panicf("RunActionCommit(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)
}
cs.Step = RoundStepCommit
@ -424,17 +433,17 @@ func (cs *ConsensusState) RunActionCommit(height uint32) []byte {
// Update mempool.
cs.mempool.ResetForBlockAndState(block, cs.stagedState)
return block.Hash()
return cs.signAddVote(VoteTypeCommit, block.Hash())
}
return nil
}
func (cs *ConsensusState) RunActionCommitWait(height uint32) {
func (cs *ConsensusState) RunActionCommitWait(height uint32, round uint16) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height {
return
if cs.Height != height || cs.Round != round {
Panicf("RunActionCommitWait(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)
}
cs.Step = RoundStepCommitWait
@ -445,11 +454,11 @@ func (cs *ConsensusState) RunActionCommitWait(height uint32) {
}
}
func (cs *ConsensusState) RunActionFinalize(height uint32) {
func (cs *ConsensusState) RunActionFinalize(height uint32, round uint16) {
cs.mtx.Lock()
defer cs.mtx.Unlock()
if cs.Height != height {
return
if cs.Height != height || cs.Round != round {
Panicf("RunActionFinalize(%v/%v), expected %v/%v", height, round, cs.Height, cs.Round)
}
// What was staged becomes committed.
@ -557,6 +566,9 @@ func (cs *ConsensusState) AddVote(vote *Vote) (added bool, err error) {
}
func (cs *ConsensusState) stageBlock(block *Block) error {
if block == nil {
panic("Cannot stage nil block")
}
// Already staged?
if cs.stagedBlock == block {
@ -577,3 +589,18 @@ func (cs *ConsensusState) stageBlock(block *Block) error {
return nil
}
}
func (cs *ConsensusState) signAddVote(type_ byte, hash []byte) *Vote {
if cs.PrivValidator == nil || !cs.Validators.HasId(cs.PrivValidator.Id) {
return nil
}
vote := &Vote{
Height: cs.Height,
Round: cs.Round,
Type: type_,
BlockHash: hash,
}
cs.PrivValidator.Sign(vote)
cs.AddVote(vote)
return vote
}

+ 29
- 37
consensus/state_test.go View File

@ -50,6 +50,15 @@ func makeConsensusState() (*ConsensusState, []*state.PrivAccount) {
return cs, privAccounts
}
func assertPanics(t *testing.T, msg string, f func()) {
defer func() {
if err := recover(); err == nil {
t.Error("Should have panic'd, but didn't. %v", msg)
}
}()
f()
}
//-----------------------------------------------------------------------------
func TestSetupRound(t *testing.T) {
@ -91,14 +100,9 @@ func TestSetupRound(t *testing.T) {
}
// Setup round 1 (should fail)
{
defer func() {
if e := recover(); e == nil {
t.Errorf("Expected to panic, round did not increment")
}
}()
assertPanics(t, "Round did not increment", func() {
cs.SetupRound(1)
}
})
}
@ -113,7 +117,7 @@ func TestRunActionProposeNoPrivValidator(t *testing.T) {
func TestRunActionPropose(t *testing.T) {
cs, privAccounts := makeConsensusState()
priv := NewPrivValidator(privAccounts[0], db_.NewMemDB())
priv := NewPrivValidator(db_.NewMemDB(), privAccounts[0])
cs.SetPrivValidator(priv)
cs.RunActionPropose(1, 0)
@ -147,28 +151,22 @@ func checkRoundState(t *testing.T, cs *ConsensusState,
func TestRunActionPrecommitCommitFinalize(t *testing.T) {
cs, privAccounts := makeConsensusState()
priv := NewPrivValidator(privAccounts[0], db_.NewMemDB())
priv := NewPrivValidator(db_.NewMemDB(), privAccounts[0])
cs.SetPrivValidator(priv)
blockHash := cs.RunActionPrecommit(1, 0)
if blockHash != nil {
t.Errorf("RunActionPrecommit should fail without a proposal")
vote := cs.RunActionPrecommit(1, 0)
if vote != nil {
t.Errorf("RunActionPrecommit should return nil without a proposal")
}
cs.RunActionPropose(1, 0)
// Test RunActionPrecommit failures:
blockHash = cs.RunActionPrecommit(1, 1)
if blockHash != nil {
t.Errorf("RunActionPrecommit should fail for wrong round")
}
blockHash = cs.RunActionPrecommit(2, 0)
if blockHash != nil {
t.Errorf("RunActionPrecommit should fail for wrong height")
}
blockHash = cs.RunActionPrecommit(1, 0)
if blockHash != nil {
t.Errorf("RunActionPrecommit should fail, not enough prevotes")
assertPanics(t, "Wrong height ", func() { cs.RunActionPrecommit(2, 0) })
assertPanics(t, "Wrong round", func() { cs.RunActionPrecommit(1, 1) })
vote = cs.RunActionPrecommit(1, 0)
if vote != nil {
t.Errorf("RunActionPrecommit should return nil, not enough prevotes")
}
// Add at least +2/3 prevotes.
@ -184,21 +182,15 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) {
}
// Test RunActionPrecommit success:
blockHash = cs.RunActionPrecommit(1, 0)
if len(blockHash) == 0 {
vote = cs.RunActionPrecommit(1, 0)
if vote == nil {
t.Errorf("RunActionPrecommit should have succeeded")
}
checkRoundState(t, cs, 1, 0, RoundStepPrecommit)
// Test RunActionCommit failures:
blockHash = cs.RunActionCommit(2)
if blockHash != nil {
t.Errorf("RunActionCommit should fail for wrong height")
}
blockHash = cs.RunActionCommit(1)
if blockHash != nil {
t.Errorf("RunActionCommit should fail, not enough commits")
}
assertPanics(t, "Wrong height ", func() { cs.RunActionCommit(2, 0) })
assertPanics(t, "Wrong round", func() { cs.RunActionCommit(1, 1) })
// Add at least +2/3 precommits.
for i := 0; i < 7; i++ {
@ -213,8 +205,8 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) {
}
// Test RunActionCommit success:
blockHash = cs.RunActionCommit(1)
if len(blockHash) == 0 {
vote = cs.RunActionCommit(1, 0)
if vote == nil {
t.Errorf("RunActionCommit should have succeeded")
}
checkRoundState(t, cs, 1, 0, RoundStepCommit)
@ -237,13 +229,13 @@ func TestRunActionPrecommitCommitFinalize(t *testing.T) {
}
// Test RunActionCommitWait:
cs.RunActionCommitWait(1)
cs.RunActionCommitWait(1, 0)
if cs.CommitTime.IsZero() {
t.Errorf("Expected CommitTime to have been set")
}
checkRoundState(t, cs, 1, 0, RoundStepCommitWait)
// Test RunActionFinalize:
cs.RunActionFinalize(1)
cs.RunActionFinalize(1, 0)
checkRoundState(t, cs, 2, 0, RoundStepStart)
}

+ 10
- 0
consensus/vote_set.go View File

@ -238,3 +238,13 @@ func (vs *VoteSet) StringWithIndent(indent string) string {
indent, vs.votesBitArray,
indent)
}
func (vs *VoteSet) Description() string {
if vs == nil {
return "nil-VoteSet"
}
vs.mtx.Lock()
defer vs.mtx.Unlock()
return fmt.Sprintf(`VoteSet{H:%v R:%v T:%v %v}`,
vs.height, vs.round, vs.type_, vs.votesBitArray)
}

+ 31
- 0
gen_account.go View File

@ -0,0 +1,31 @@
// +build gen_account
package main
import (
"encoding/base64"
"fmt"
"github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/state"
)
func main() {
// Parse config flags
config.ParseFlags()
// Generate private account
privAccount := state.GenPrivAccount()
fmt.Printf(`Generated account:
Account Public Key: %X
(base64) %v
Account Private Key: %X
(base64) %v
`,
privAccount.PubKey,
base64.StdEncoding.EncodeToString(privAccount.PubKey),
privAccount.PrivKey,
base64.StdEncoding.EncodeToString(privAccount.PrivKey))
}

+ 1
- 1
p2p/switch.go View File

@ -170,7 +170,7 @@ func (sw *Switch) Broadcast(chId byte, msg Binary) (numSuccess, numFailure int)
return
}
log.Debug("Broadcast on [%X]", chId, msg)
log.Debug("[%X] Broadcast: %v", chId, msg)
for _, peer := range sw.peers.List() {
success := peer.TrySend(chId, msg)
log.Debug("Broadcast for peer %v success: %v", peer, success)


+ 20
- 0
state/account.go View File

@ -1,8 +1,10 @@
package state
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
. "github.com/tendermint/tendermint/binary"
. "github.com/tendermint/tendermint/blocks"
@ -138,6 +140,24 @@ func GenPrivAccount() *PrivAccount {
}
}
// The Account.Id is empty since it isn't in the blockchain.
func PrivAccountFromJSON(jsonBlob []byte) (privAccount *PrivAccount) {
err := json.Unmarshal(jsonBlob, &privAccount)
if err != nil {
Panicf("Couldn't read PrivAccount: %v", err)
}
return
}
// The Account.Id is empty since it isn't in the blockchain.
func PrivAccountFromFile(file string) *PrivAccount {
jsonBlob, err := ioutil.ReadFile(file)
if err != nil {
Panicf("Couldn't read PrivAccount from file: %v", err)
}
return PrivAccountFromJSON(jsonBlob)
}
func (pa *PrivAccount) SignBytes(msg []byte) Signature {
signature := crypto.SignMessage(msg, pa.PrivKey, pa.PubKey)
sig := Signature{


+ 2
- 2
state/genesis.go View File

@ -16,7 +16,7 @@ type GenesisDoc struct {
AccountDetails []*AccountDetail
}
func ReadGenesisDocJSON(jsonBlob []byte) (genState *GenesisDoc) {
func GenesisDocFromJSON(jsonBlob []byte) (genState *GenesisDoc) {
err := json.Unmarshal(jsonBlob, &genState)
if err != nil {
Panicf("Couldn't read GenesisDoc: %v", err)
@ -29,7 +29,7 @@ func GenesisStateFromFile(db db_.DB, genDocFile string) *State {
if err != nil {
Panicf("Couldn't read GenesisDoc file: %v", err)
}
genDoc := ReadGenesisDocJSON(jsonBlob)
genDoc := GenesisDocFromJSON(jsonBlob)
return GenesisStateFromDoc(db, genDoc)
}


+ 5
- 0
state/validator_set.go View File

@ -81,6 +81,11 @@ func (vset *ValidatorSet) GetById(id uint64) (index uint32, val *Validator) {
return
}
func (vset *ValidatorSet) HasId(id uint64) bool {
_, val_ := vset.validators.Get(id)
return val_ != nil
}
func (vset *ValidatorSet) GetByIndex(index uint32) (id uint64, val *Validator) {
id_, val_ := vset.validators.GetByIndex(uint64(index))
id, val = id_.(uint64), val_.(*Validator)


main.go → tendermintd.go View File


Loading…
Cancel
Save