Skip to content

Commit

Permalink
Merge pull request #1 from leapdao/feature/set-readonly
Browse files Browse the repository at this point in the history
Allow to run validator without producing blocks
  • Loading branch information
troggy authored Mar 11, 2019
2 parents a49e07d + f5f07b1 commit d09a9cd
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 16 deletions.
1 change: 1 addition & 0 deletions cmd/tendermint/commands/run_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func AddNodeFlags(cmd *cobra.Command) {

// consensus flags
cmd.Flags().Bool("consensus.create_empty_blocks", config.Consensus.CreateEmptyBlocks, "Set this to false to only produce blocks when there are txs or when the AppHash changes")
cmd.Flags().Bool("consensus.readonly", config.Consensus.Readonly, "Set this to true to make validator skip producing blocks")
}

// NewRunNodeCmd returns the command that allows the CLI to start a node.
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,6 +587,8 @@ type ConsensusConfig struct {

// Block time parameters. Corresponds to the minimum time increment between consecutive blocks.
BlockTimeIota time.Duration `mapstructure:"blocktime_iota"`

Readonly bool `mapstructure:"readonly"`
}

// DefaultConsensusConfig returns a default configuration for the consensus service
Expand All @@ -606,6 +608,7 @@ func DefaultConsensusConfig() *ConsensusConfig {
PeerGossipSleepDuration: 100 * time.Millisecond,
PeerQueryMaj23SleepDuration: 2000 * time.Millisecond,
BlockTimeIota: 1000 * time.Millisecond,
Readonly: false,
}
}

Expand Down
3 changes: 3 additions & 0 deletions config/toml.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ peer_query_maj23_sleep_duration = "{{ .Consensus.PeerQueryMaj23SleepDuration }}"
# Block time parameters. Corresponds to the minimum time increment between consecutive blocks.
blocktime_iota = "{{ .Consensus.BlockTimeIota }}"
# Do not produce blocks, just observe (for validator)
readonly = {{ .Consensus.Readonly }}
##### transactions indexer configuration options #####
[tx_index]
Expand Down
71 changes: 55 additions & 16 deletions consensus/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"runtime/debug"
"sync"
"time"
"strconv"

cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/fail"
Expand Down Expand Up @@ -127,6 +128,8 @@ type ConsensusState struct {

// for reporting metrics
metrics *Metrics

readonly bool
}

// StateOption sets an optional parameter on the ConsensusState.
Expand Down Expand Up @@ -159,6 +162,7 @@ func NewConsensusState(
evpool: evpool,
evsw: tmevents.NewEventSwitch(),
metrics: NopMetrics(),
readonly: config.Readonly,
}
// set function defaults (may be overwritten before calling Start)
cs.decideProposal = cs.defaultDecideProposal
Expand Down Expand Up @@ -808,14 +812,35 @@ func (cs *ConsensusState) needProofBlock(height int64) bool {
return !bytes.Equal(cs.state.AppHash, lastBlockMeta.Header.AppHash)
}

func (cs *ConsensusState) isValidator() bool {
return cs.privValidator != nil &&
cs.Validators.HasAddress(cs.privValidator.GetAddress())
}

func (cs *ConsensusState) SetReadonly(readonly bool) {
cs.readonly = readonly
cs.Logger.Info("[SetReadonly]: Set to " + strconv.FormatBool(readonly) + " " + strconv.FormatBool(cs.readonly))
}

func (cs *ConsensusState) IsReadonly() bool {
return cs.readonly;
}

func (cs *ConsensusState) proposalHeartbeat(height int64, round int) {
logger := cs.Logger.With("height", height, "round", round)
addr := cs.privValidator.GetAddress()

if !cs.Validators.HasAddress(addr) {
logger.Debug("Not sending proposalHearbeat. This node is not a validator", "addr", addr, "vals", cs.Validators)
if !cs.isValidator() {
logger.Info("Not sending proposalHearbeat. This node is not a validator", "addr", addr, "vals", cs.Validators)
return
}

// validator is readonly, do nothing
if (cs.readonly) {
cs.Logger.Info("proposalHeartbeat: Validator is in readonly mode")
return
}

counter := 0
valIndex, _ := cs.Validators.GetByAddress(addr)
chainID := cs.state.ChainID
Expand Down Expand Up @@ -852,6 +877,19 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
}
logger.Info(fmt.Sprintf("enterPropose(%v/%v). Current: %v/%v/%v", height, round, cs.Height, cs.Round, cs.Step))

// Nothing more to do if we're not a validator
if cs.privValidator == nil {
logger.Debug("This node is not a validator 1")
return
}

// if not a validator, we're done
if !cs.isValidator() {
logger.Debug("This node is not a validator 2", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators)
return
}
logger.Debug("This node is a validator " + strconv.FormatBool(cs.readonly))

defer func() {
// Done enterPropose:
cs.updateRoundStep(round, cstypes.RoundStepPropose)
Expand All @@ -868,21 +906,15 @@ func (cs *ConsensusState) enterPropose(height int64, round int) {
// If we don't get the proposal and all block parts quick enough, enterPrevote
cs.scheduleTimeout(cs.config.Propose(round), height, round, cstypes.RoundStepPropose)

// Nothing more to do if we're not a validator
if cs.privValidator == nil {
logger.Debug("This node is not a validator 1")
return
}

// if not a validator, we're done
if !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
logger.Debug("This node is not a validator 2", "addr", cs.privValidator.GetAddress(), "vals", cs.Validators)
return
}
logger.Debug("This node is a validator")

if cs.isProposer() {
logger.Info("enterPropose: Our turn to propose", "proposer", cs.Validators.GetProposer().Address, "privValidator", cs.privValidator)

// validator is readonly, do nothing
if (cs.readonly) {
logger.Info("enterPropose: Validator is in readonly mode. Skipping..")
return
}

if height%32 == 0 {
rsp, err := cs.proxyApp.CheckBridgeSync(abci.RequestCheckBridge{Height: int32(round)})
if err != nil {
Expand Down Expand Up @@ -1736,9 +1768,16 @@ func (cs *ConsensusState) voteTime() time.Time {
// sign the vote and publish on internalMsgQueue
func (cs *ConsensusState) signAddVote(type_ types.SignedMsgType, hash []byte, header types.PartSetHeader) *types.Vote {
// if we don't have a key or we're not in the validator set, do nothing
if cs.privValidator == nil || !cs.Validators.HasAddress(cs.privValidator.GetAddress()) {
if !cs.isValidator() {
return nil
}

// validator is readonly, do nothing
if (cs.readonly) {
cs.Logger.Info("signAddVote: Validator is in readonly mode")
return nil
}

vote, err := cs.signVote(type_, hash, header)
if err == nil {
cs.sendInternalMessage(msgInfo{&VoteMessage{vote}, ""})
Expand Down
2 changes: 2 additions & 0 deletions rpc/core/pipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Consensus interface {
GetLastHeight() int64
GetRoundStateJSON() ([]byte, error)
GetRoundStateSimpleJSON() ([]byte, error)
SetReadonly(readonly bool)
IsReadonly() bool
}

type transport interface {
Expand Down
2 changes: 2 additions & 0 deletions rpc/core/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,6 @@ func AddUnsafeRoutes() {
Routes["unsafe_start_cpu_profiler"] = rpc.NewRPCFunc(UnsafeStartCPUProfiler, "filename")
Routes["unsafe_stop_cpu_profiler"] = rpc.NewRPCFunc(UnsafeStopCPUProfiler, "")
Routes["unsafe_write_heap_profile"] = rpc.NewRPCFunc(UnsafeWriteHeapProfile, "filename")

Routes["set_readonly"] = rpc.NewRPCFunc(SetReadonly, "readonly")
}
8 changes: 8 additions & 0 deletions rpc/core/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package core
import (
"bytes"
"time"
"strconv"

cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/p2p"
Expand Down Expand Up @@ -105,6 +106,7 @@ func Status() (*ctypes.ResultStatus, error) {
Address: pubKey.Address(),
PubKey: pubKey,
VotingPower: votingPower,
IsReadonly: consensusState.IsReadonly(),
},
}

Expand Down Expand Up @@ -136,3 +138,9 @@ func validatorAtHeight(h int64) *types.Validator {

return nil
}

func SetReadonly(readonly bool) (*ctypes.ResultSetReadonly, error) {
consensusState.SetReadonly(readonly)

return &ctypes.ResultSetReadonly{"Set validator as readonly: " + strconv.FormatBool(readonly)}, nil
}
5 changes: 5 additions & 0 deletions rpc/core/types/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ type ValidatorInfo struct {
Address cmn.HexBytes `json:"address"`
PubKey crypto.PubKey `json:"pub_key"`
VotingPower int64 `json:"voting_power"`
IsReadonly bool `json:"is_readonly"`
}

// Node Status
Expand Down Expand Up @@ -105,6 +106,10 @@ type ResultDialPeers struct {
Log string `json:"log"`
}

type ResultSetReadonly struct {
Log string `json:"log"`
}

// A peer
type Peer struct {
NodeInfo p2p.DefaultNodeInfo `json:"node_info"`
Expand Down

0 comments on commit d09a9cd

Please sign in to comment.