github.com/pokt-network/tendermint@v0.32.11-0.20230426215212-59310158d3e9/docs/architecture/adr-030-consensus-refactor.md (about)

     1  # ADR 030: Consensus Refactor
     2  
     3  ## Context
     4  
     5  One of the biggest challenges this project faces is to proof that the
     6  implementations of the specifications are correct, much like we strive to
     7  formaly verify our alogrithms and protocols we should work towards high
     8  confidence about the correctness of our program code. One of those is the core
     9  of Tendermint - Consensus - which currently resides in the `consensus` package.
    10  Over time there has been high friction making changes to the package due to the
    11  algorithm being scattered in a side-effectful container (the current
    12  `ConsensusState`). In order to test the algorithm a large object-graph needs to
    13  be set up and even than the non-deterministic parts of the container makes will
    14  prevent high certainty. Where ideally we have a 1-to-1 representation of the
    15  [spec](https://github.com/tendermint/spec), ready and easy to test for domain
    16  experts.
    17  
    18  Addresses:
    19  
    20  - [#1495](https://github.com/tendermint/tendermint/issues/1495)
    21  - [#1692](https://github.com/tendermint/tendermint/issues/1692)
    22  
    23  ## Decision
    24  
    25  To remedy these issues we plan a gradual, non-invasive refactoring of the
    26  `consensus` package. Starting of by isolating the consensus alogrithm into
    27  a pure function and a finite state machine to address the most pressuring issue
    28  of lack of confidence. Doing so while leaving the rest of the package in tact
    29  and have follow-up optional changes to improve the sepration of concerns.
    30  
    31  ### Implementation changes
    32  
    33  The core of Consensus can be modelled as a function with clear defined inputs:
    34  
    35  * `State` - data container for current round, height, etc.
    36  * `Event`- significant events in the network
    37  
    38  producing clear outputs;
    39  
    40  * `State` - updated input
    41  * `Message` - signal what actions to perform
    42  
    43  ```go
    44  type Event int
    45  
    46  const (
    47  	EventUnknown Event = iota
    48  	EventProposal
    49  	Majority23PrevotesBlock
    50  	Majority23PrecommitBlock
    51  	Majority23PrevotesAny
    52  	Majority23PrecommitAny
    53  	TimeoutNewRound
    54  	TimeoutPropose
    55  	TimeoutPrevotes
    56  	TimeoutPrecommit
    57  )
    58  
    59  type Message int
    60  
    61  const (
    62  	MeesageUnknown Message = iota
    63  	MessageProposal
    64  	MessageVotes
    65  	MessageDecision
    66  )
    67  
    68  type State struct {
    69  	height      uint64
    70  	round       uint64
    71  	step        uint64
    72  	lockedValue interface{} // TODO: Define proper type.
    73  	lockedRound interface{} // TODO: Define proper type.
    74  	validValue  interface{} // TODO: Define proper type.
    75  	validRound  interface{} // TODO: Define proper type.
    76  	// From the original notes: valid(v)
    77  	valid       interface{} // TODO: Define proper type.
    78  	// From the original notes: proposer(h, r)
    79  	proposer    interface{} // TODO: Define proper type.
    80  }
    81  
    82  func Consensus(Event, State) (State, Message) {
    83  	// Consolidate implementation.
    84  }
    85  ```
    86  
    87  Tracking of relevant information to feed `Event` into the function and act on
    88  the output is left to the `ConsensusExecutor` (formerly `ConsensusState`). 
    89  
    90  Benefits for testing surfacing nicely as testing for a sequence of events
    91  against algorithm could be as simple as the following example:
    92  
    93  ``` go
    94  func TestConsensusXXX(t *testing.T) {
    95  	type expected struct {
    96  		message Message
    97  		state   State
    98  	}
    99  
   100  	// Setup order of events, initial state and expectation.
   101  	var (
   102  		events = []struct {
   103  			event Event
   104  			want  expected
   105  		}{
   106  		// ...
   107  		}
   108  		state = State{
   109  		// ...
   110  		}
   111  	)
   112  
   113  	for _, e := range events {
   114  		sate, msg = Consensus(e.event, state)
   115  
   116  		// Test message expectation.
   117  		if msg != e.want.message {
   118  			t.Fatalf("have %v, want %v", msg, e.want.message)
   119  		}
   120  
   121  		// Test state expectation.
   122  		if !reflect.DeepEqual(state, e.want.state) {
   123  			t.Fatalf("have %v, want %v", state, e.want.state)
   124  		}
   125  	}
   126  }
   127  ```
   128  
   129  
   130  ## Consensus Executor
   131  
   132  ## Consensus Core
   133  
   134  ```go
   135  type Event interface{}
   136  
   137  type EventNewHeight struct {
   138      Height           int64
   139      ValidatorId      int
   140  }
   141  
   142  type EventNewRound HeightAndRound
   143  
   144  type EventProposal struct {
   145      Height           int64
   146      Round            int
   147      Timestamp        Time
   148      BlockID          BlockID
   149      POLRound         int
   150      Sender           int   
   151  }
   152  
   153  type Majority23PrevotesBlock struct {
   154      Height           int64
   155      Round            int
   156      BlockID          BlockID
   157  }
   158  
   159  type Majority23PrecommitBlock struct {
   160      Height           int64
   161      Round            int
   162      BlockID          BlockID
   163  }
   164  
   165  type HeightAndRound struct {
   166      Height           int64
   167      Round            int
   168  }
   169  
   170  type Majority23PrevotesAny HeightAndRound
   171  type Majority23PrecommitAny HeightAndRound
   172  type TimeoutPropose HeightAndRound
   173  type TimeoutPrevotes HeightAndRound
   174  type TimeoutPrecommit HeightAndRound
   175  
   176  
   177  type Message interface{}
   178  
   179  type MessageProposal struct {
   180      Height           int64
   181      Round            int
   182      BlockID          BlockID
   183      POLRound         int
   184  }
   185  
   186  type VoteType int
   187  
   188  const (
   189  	VoteTypeUnknown VoteType = iota
   190  	Prevote
   191  	Precommit
   192  )
   193  
   194  
   195  type MessageVote struct {
   196      Height           int64
   197      Round            int
   198      BlockID          BlockID
   199      Type             VoteType
   200  }
   201  
   202  
   203  type MessageDecision struct {
   204      Height           int64
   205      Round            int
   206      BlockID          BlockID
   207  }
   208  
   209  type TriggerTimeout struct {
   210      Height           int64
   211      Round            int
   212      Duration         Duration
   213  }
   214  
   215  
   216  type RoundStep int
   217  
   218  const (
   219  	RoundStepUnknown RoundStep = iota
   220  	RoundStepPropose       
   221  	RoundStepPrevote
   222  	RoundStepPrecommit
   223  	RoundStepCommit
   224  )
   225  
   226  type State struct {
   227  	Height           int64
   228  	Round            int
   229  	Step             RoundStep
   230  	LockedValue      BlockID
   231  	LockedRound      int
   232  	ValidValue       BlockID
   233  	ValidRound       int
   234  	ValidatorId      int
   235  	ValidatorSetSize int
   236  }
   237  
   238  func proposer(height int64, round int) int {}
   239  func getValue() BlockID {}
   240  
   241  func Consensus(event Event, state State) (State, Message, TriggerTimeout) {
   242      msg = nil
   243      timeout = nil
   244  	switch event := event.(type) {
   245      	case EventNewHeight:
   246      		if event.Height > state.Height {
   247      		    state.Height = event.Height
   248      		    state.Round = -1
   249      		    state.Step = RoundStepPropose
   250      		    state.LockedValue = nil
   251      		    state.LockedRound = -1
   252      		    state.ValidValue = nil
   253      		    state.ValidRound = -1
   254      		    state.ValidatorId = event.ValidatorId
   255      		} 
   256      	    return state, msg, timeout
   257      	
   258      	case EventNewRound:
   259      		if event.Height == state.Height and event.Round > state.Round {
   260                 state.Round = eventRound
   261                 state.Step = RoundStepPropose
   262                 if proposer(state.Height, state.Round) == state.ValidatorId {
   263                     proposal = state.ValidValue
   264                     if proposal == nil {
   265                     	    proposal = getValue()
   266                     }
   267                     msg =  MessageProposal { state.Height, state.Round, proposal, state.ValidRound }
   268                 }
   269                 timeout = TriggerTimeout { state.Height, state.Round, timeoutPropose(state.Round) }
   270              }
   271      	    return state, msg, timeout
   272      	
   273      	case EventProposal:
   274      		if event.Height == state.Height and event.Round == state.Round and 
   275      	       event.Sender == proposal(state.Height, state.Round) and state.Step == RoundStepPropose { 
   276      	       	if event.POLRound >= state.LockedRound or event.BlockID == state.BlockID or state.LockedRound == -1 {
   277      	       		msg = MessageVote { state.Height, state.Round, event.BlockID, Prevote }
   278      	       	}
   279      	       	state.Step = RoundStepPrevote
   280              }
   281      	    return state, msg, timeout
   282      	
   283      	case TimeoutPropose:
   284      		if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPropose {
   285      		    msg = MessageVote { state.Height, state.Round, nil, Prevote }
   286      			state.Step = RoundStepPrevote
   287              }
   288      	    return state, msg, timeout
   289      	
   290      	case Majority23PrevotesBlock:
   291      		if event.Height == state.Height and event.Round == state.Round and state.Step >= RoundStepPrevote and event.Round > state.ValidRound {
   292      		    state.ValidRound = event.Round
   293      		    state.ValidValue = event.BlockID
   294      		    if state.Step == RoundStepPrevote {
   295      		    	state.LockedRound = event.Round
   296      		    	state.LockedValue = event.BlockID
   297      		    	msg = MessageVote { state.Height, state.Round, event.BlockID, Precommit }
   298      		    	state.Step = RoundStepPrecommit
   299      		    }
   300              }
   301      	    return state, msg, timeout
   302      	
   303      	case Majority23PrevotesAny:
   304      		if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
   305      			timeout = TriggerTimeout { state.Height, state.Round, timeoutPrevote(state.Round) }
   306      		}
   307      	    return state, msg, timeout
   308      	
   309      	case TimeoutPrevote:
   310      		if event.Height == state.Height and event.Round == state.Round and state.Step == RoundStepPrevote {
   311      			msg = MessageVote { state.Height, state.Round, nil, Precommit }
   312      			state.Step = RoundStepPrecommit
   313      		}
   314      	    return state, msg, timeout
   315      	
   316      	case Majority23PrecommitBlock:
   317      		if event.Height == state.Height {
   318      		    state.Step = RoundStepCommit
   319      		    state.LockedValue = event.BlockID
   320      		}
   321      	    return state, msg, timeout
   322      		
   323      	case Majority23PrecommitAny:
   324      		if event.Height == state.Height and event.Round == state.Round {
   325      			timeout = TriggerTimeout { state.Height, state.Round, timeoutPrecommit(state.Round) }
   326      		}
   327      	    return state, msg, timeout
   328      	
   329      	case TimeoutPrecommit:
   330              if event.Height == state.Height and event.Round == state.Round {
   331              	state.Round = state.Round + 1
   332              }
   333      	    return state, msg, timeout
   334  	}
   335  }	
   336  
   337  func ConsensusExecutor() {
   338  	proposal = nil
   339  	votes = HeightVoteSet { Height: 1 }
   340  	state = State {
   341  		Height:       1
   342  		Round:        0          
   343  		Step:         RoundStepPropose
   344  		LockedValue:  nil
   345  		LockedRound:  -1
   346  		ValidValue:   nil
   347  		ValidRound:   -1
   348  	}
   349  	
   350  	event = EventNewHeight {1, id}
   351  	state, msg, timeout = Consensus(event, state)
   352  	
   353  	event = EventNewRound {state.Height, 0}
   354  	state, msg, timeout = Consensus(event, state)
   355  	
   356  	if msg != nil {
   357  		send msg
   358  	}
   359  	
   360  	if timeout != nil {
   361  		trigger timeout
   362  	}
   363  	
   364  	for {
   365  		select {
   366  		    case message := <- msgCh:
   367  		    	switch msg := message.(type) {
   368  		    	    case MessageProposal:
   369  		    	        
   370  		    	    case MessageVote:	
   371  		    	    	if msg.Height == state.Height {
   372  		    	    		newVote = votes.AddVote(msg)
   373  		    	    		if newVote {
   374  		    	    			switch msg.Type {
   375                                  	case Prevote:
   376                                  		prevotes = votes.Prevotes(msg.Round)
   377                                  		if prevotes.WeakCertificate() and msg.Round > state.Round {
   378                                  			event = EventNewRound { msg.Height, msg.Round }
   379                                  			state, msg, timeout = Consensus(event, state)
   380                                  			state = handleStateChange(state, msg, timeout)
   381                                  		}	
   382                                  		
   383                                  		if blockID, ok = prevotes.TwoThirdsMajority(); ok and blockID != nil {
   384                                  		    if msg.Round == state.Round and hasBlock(blockID) {
   385                                  		    	event = Majority23PrevotesBlock { msg.Height, msg.Round, blockID }
   386                                  		    	state, msg, timeout = Consensus(event, state)
   387                                  		    	state = handleStateChange(state, msg, timeout)
   388                                  		    } 
   389                                  		    if proposal != nil and proposal.POLRound == msg.Round and hasBlock(blockID) {
   390                                  		        event = EventProposal {
   391                                                          Height: state.Height
   392                                                          Round:  state.Round
   393                                                          BlockID: blockID
   394                                                          POLRound: proposal.POLRound
   395                                                          Sender: message.Sender
   396                                  		        }
   397                                  		        state, msg, timeout = Consensus(event, state)
   398                                  		        state = handleStateChange(state, msg, timeout)
   399                                  		    }
   400                                  		}
   401                                  		
   402                                  		if prevotes.HasTwoThirdsAny() and msg.Round == state.Round {
   403                                  			event = Majority23PrevotesAny { msg.Height, msg.Round, blockID }
   404                                  			state, msg, timeout = Consensus(event, state)
   405                                              state = handleStateChange(state, msg, timeout)
   406                                  		}
   407                                  		
   408                                  	case Precommit:	
   409                                  		
   410  		    	    		    }
   411  		    	    	    }
   412  		    	        }
   413  		    case timeout := <- timeoutCh:
   414  		    
   415  		    case block := <- blockCh:	
   416  		    	
   417  		}
   418  	}
   419  }
   420  	
   421  func handleStateChange(state, msg, timeout) State {
   422  	if state.Step == Commit {
   423  		state = ExecuteBlock(state.LockedValue)
   424  	}	
   425  	if msg != nil {
   426  		send msg
   427  	}	
   428  	if timeout != nil {
   429  		trigger timeout
   430  	}	
   431  }
   432  
   433  ```
   434  
   435  ### Implementation roadmap
   436  
   437  * implement proposed implementation
   438  * replace currently scattered calls in `ConsensusState` with calls to the new
   439    `Consensus` function
   440  * rename `ConsensusState` to `ConsensusExecutor` to avoid confusion
   441  * propose design for improved separation and clear information flow between
   442    `ConsensusExecutor` and `ConsensusReactor`
   443  
   444  ## Status
   445  
   446  Draft.
   447  
   448  ## Consequences
   449  
   450  ### Positive
   451  
   452  - isolated implementation of the algorithm
   453  - improved testability - simpler to proof correctness
   454  - clearer separation of concerns - easier to reason
   455  
   456  ### Negative
   457  
   458  ### Neutral