mirror of
https://github.com/tendermint/tendermint.git
synced 2026-09-13 11:34:17 +00:00
ci: add markdown linter (#146)
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
# Blockchain Reactor v1
|
||||
|
||||
### Data Structures
|
||||
## Data Structures
|
||||
|
||||
The data structures used are illustrated below.
|
||||
|
||||

|
||||
|
||||
#### BlockchainReactor
|
||||
### BlockchainReactor
|
||||
|
||||
- is a `p2p.BaseReactor`.
|
||||
- has a `store.BlockStore` for persistence.
|
||||
- executes blocks using an `sm.BlockExecutor`.
|
||||
@@ -17,33 +19,34 @@ The data structures used are illustrated below.
|
||||
|
||||
```go
|
||||
type BlockchainReactor struct {
|
||||
p2p.BaseReactor
|
||||
p2p.BaseReactor
|
||||
|
||||
initialState sm.State // immutable
|
||||
state sm.State
|
||||
initialState sm.State // immutable
|
||||
state sm.State
|
||||
|
||||
blockExec *sm.BlockExecutor
|
||||
store *store.BlockStore
|
||||
blockExec *sm.BlockExecutor
|
||||
store *store.BlockStore
|
||||
|
||||
fastSync bool
|
||||
fastSync bool
|
||||
|
||||
fsm *BcReactorFSM
|
||||
blocksSynced int
|
||||
fsm *BcReactorFSM
|
||||
blocksSynced int
|
||||
|
||||
// Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine.
|
||||
messagesForFSMCh chan bcReactorMessage
|
||||
// Receive goroutine forwards messages to this channel to be processed in the context of the poolRoutine.
|
||||
messagesForFSMCh chan bcReactorMessage
|
||||
|
||||
// Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed
|
||||
// to this channel to be processed in the context of the poolRoutine.
|
||||
errorsForFSMCh chan bcReactorMessage
|
||||
// Switch goroutine may send RemovePeer to the blockchain reactor. This is an error message that is relayed
|
||||
// to this channel to be processed in the context of the poolRoutine.
|
||||
errorsForFSMCh chan bcReactorMessage
|
||||
|
||||
// This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and
|
||||
// the switch.
|
||||
eventsFromFSMCh chan bcFsmMessage
|
||||
// This channel is used by the FSM and indirectly the block pool to report errors to the blockchain reactor and
|
||||
// the switch.
|
||||
eventsFromFSMCh chan bcFsmMessage
|
||||
}
|
||||
```
|
||||
|
||||
#### BcReactorFSM
|
||||
|
||||
- implements a simple finite state machine.
|
||||
- has a state and a state timer.
|
||||
- has a `BlockPool` to keep track of block requests sent to peers and blocks received from peers.
|
||||
@@ -51,49 +54,53 @@ type BlockchainReactor struct {
|
||||
|
||||
```go
|
||||
type BcReactorFSM struct {
|
||||
logger log.Logger
|
||||
mtx sync.Mutex
|
||||
logger log.Logger
|
||||
mtx sync.Mutex
|
||||
|
||||
startTime time.Time
|
||||
startTime time.Time
|
||||
|
||||
state *bcReactorFSMState
|
||||
stateTimer *time.Timer
|
||||
pool *BlockPool
|
||||
state *bcReactorFSMState
|
||||
stateTimer *time.Timer
|
||||
pool *BlockPool
|
||||
|
||||
// interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc.
|
||||
toBcR bcReactor
|
||||
// interface used to call the Blockchain reactor to send StatusRequest, BlockRequest, reporting errors, etc.
|
||||
toBcR bcReactor
|
||||
}
|
||||
```
|
||||
|
||||
#### BlockPool
|
||||
|
||||
- maintains a peer set, implemented as a map of peer ID to `BpPeer`.
|
||||
- maintains a set of requests made to peers, implemented as a map of block request heights to peer IDs.
|
||||
- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights.
|
||||
- maintains a list of future block requests needed to advance the fast-sync. This is a list of block heights.
|
||||
- keeps track of the maximum height of the peers in the set.
|
||||
- uses an interface to send requests and report errors to the reactor (via FSM).
|
||||
|
||||
```go
|
||||
type BlockPool struct {
|
||||
logger log.Logger
|
||||
// Set of peers that have sent status responses, with height bigger than pool.Height
|
||||
peers map[p2p.ID]*BpPeer
|
||||
// Set of block heights and the corresponding peers from where a block response is expected or has been received.
|
||||
blocks map[int64]p2p.ID
|
||||
logger log.Logger
|
||||
// Set of peers that have sent status responses, with height bigger than pool.Height
|
||||
peers map[p2p.ID]*BpPeer
|
||||
// Set of block heights and the corresponding peers from where a block response is expected or has been received.
|
||||
blocks map[int64]p2p.ID
|
||||
|
||||
plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest
|
||||
nextRequestHeight int64 // next height to be added to plannedRequests
|
||||
plannedRequests map[int64]struct{} // list of blocks to be assigned peers for blockRequest
|
||||
nextRequestHeight int64 // next height to be added to plannedRequests
|
||||
|
||||
Height int64 // height of next block to execute
|
||||
MaxPeerHeight int64 // maximum height of all peers
|
||||
toBcR bcReactor
|
||||
Height int64 // height of next block to execute
|
||||
MaxPeerHeight int64 // maximum height of all peers
|
||||
toBcR bcReactor
|
||||
}
|
||||
```
|
||||
|
||||
Some reasons for the `BlockPool` data structure content:
|
||||
|
||||
1. If a peer is removed by the switch fast access is required to the peer and the block requests made to that peer in order to redo them.
|
||||
2. When block verification fails fast access is required from the block height to the peer and the block requests made to that peer in order to redo them.
|
||||
3. The `BlockchainReactor` main routine decides when the block pool is running low and asks the `BlockPool` (via FSM) to make more requests. The `BlockPool` creates a list of requests and triggers the sending of the block requests (via the interface). The reason it maintains a list of requests is the redo operations that may occur during error handling. These are redone when the `BlockchainReactor` requires more blocks.
|
||||
|
||||
#### BpPeer
|
||||
|
||||
- keeps track of a single peer, with height bigger than the initial height.
|
||||
- maintains the block requests made to the peer and the blocks received from the peer until they are executed.
|
||||
- monitors the peer speed when there are pending requests.
|
||||
@@ -101,17 +108,17 @@ Some reasons for the `BlockPool` data structure content:
|
||||
|
||||
```go
|
||||
type BpPeer struct {
|
||||
logger log.Logger
|
||||
ID p2p.ID
|
||||
logger log.Logger
|
||||
ID p2p.ID
|
||||
|
||||
Height int64 // the peer reported height
|
||||
NumPendingBlockRequests int // number of requests still waiting for block responses
|
||||
blocks map[int64]*types.Block // blocks received or expected to be received from this peer
|
||||
blockResponseTimer *time.Timer
|
||||
recvMonitor *flow.Monitor
|
||||
params *BpPeerParams // parameters for timer and monitor
|
||||
Height int64 // the peer reported height
|
||||
NumPendingBlockRequests int // number of requests still waiting for block responses
|
||||
blocks map[int64]*types.Block // blocks received or expected to be received from this peer
|
||||
blockResponseTimer *time.Timer
|
||||
recvMonitor *flow.Monitor
|
||||
params *BpPeerParams // parameters for timer and monitor
|
||||
|
||||
onErr func(err error, peerID p2p.ID) // function to call on error
|
||||
onErr func(err error, peerID p2p.ID) // function to call on error
|
||||
}
|
||||
```
|
||||
|
||||
@@ -120,61 +127,73 @@ type BpPeer struct {
|
||||
The diagram below shows the goroutines (depicted by the gray blocks), timers (shown on the left with their values) and channels (colored rectangles). The FSM box shows some of the functionality and it is not a separate goroutine.
|
||||
|
||||
The interface used by the FSM is shown in light red with the `IF` block. This is used to:
|
||||
- send block requests
|
||||
|
||||
- send block requests
|
||||
- report peer errors to the switch - this results in the reactor calling `switch.StopPeerForError()` and, if triggered by the peer timeout routine, a `removePeerEv` is sent to the FSM and action is taken from the context of the `poolRoutine()`
|
||||
- ask the reactor to reset the state timers. The timers are owned by the FSM while the timeout routine is defined by the reactor. This was done in order to avoid running timers in tests and will change in the next revision.
|
||||
|
||||
|
||||
There are two main goroutines implemented by the blockchain reactor. All I/O operations are performed from the `poolRoutine()` context while the CPU intensive operations related to the block execution are performed from the context of the `executeBlocksRoutine()`. All goroutines are detailed in the next sections.
|
||||
|
||||

|
||||
|
||||
#### Receive()
|
||||
|
||||
Fast-sync messages from peers are received by this goroutine. It performs basic validation and:
|
||||
|
||||
- in helper mode (i.e. for request message) it replies immediately. This is different than the proposal in adr-040 that specifies having the FSM handling these.
|
||||
- forwards response messages to the `poolRoutine()`.
|
||||
|
||||
#### poolRoutine()
|
||||
(named kept as in the previous reactor).
|
||||
|
||||
(named kept as in the previous reactor).
|
||||
It starts the `executeBlocksRoutine()` and the FSM. It then waits in a loop for events. These are received from the following channels:
|
||||
|
||||
- `sendBlockRequestTicker.C` - every 10msec the reactor asks FSM to make more block requests up to a maximum. Note: currently this value is constant but could be changed based on low/ high watermark thresholds for the number of blocks received and waiting to be processed, the number of blockResponse messages waiting in messagesForFSMCh, etc.
|
||||
- `statusUpdateTicker.C` - every 10 seconds the reactor broadcasts status requests to peers. While adr-040 specifies this to run within the FSM, at this point this functionality is kept in the reactor.
|
||||
- `messagesForFSMCh` - the `Receive()` goroutine sends status and block response messages to this channel and the reactor calls FSM to handle them.
|
||||
- `errorsForFSMCh` - this channel receives the following events:
|
||||
- `errorsForFSMCh` - this channel receives the following events:
|
||||
- peer remove - when the switch removes a peer
|
||||
- sate timeout event - when FSM state timers trigger
|
||||
The reactor forwards this messages to the FSM.
|
||||
- `eventsFromFSMCh` - there are two type of events sent over this channel:
|
||||
- `syncFinishedEv` - triggered when FSM enters `finished` state and calls the switchToConsensus() interface function.
|
||||
- `peerErrorEv`- peer timer expiry goroutine sends this event over the channel for processing from poolRoutine() context.
|
||||
|
||||
#### executeBlocksRoutine()
|
||||
Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them:
|
||||
- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel.
|
||||
- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed.
|
||||
|
||||
#### executeBlocksRoutine()
|
||||
|
||||
Started by the `poolRoutine()`, it retrieves blocks from the pool and executes them:
|
||||
|
||||
- `processReceivedBlockTicker.C` - a ticker event is received over the channel every 10msec and its handling results in a signal being sent to the doProcessBlockCh channel.
|
||||
- doProcessBlockCh - events are received on this channel as described as above and upon processing blocks are retrieved from the pool and executed.
|
||||
|
||||
### FSM
|
||||
|
||||

|
||||
|
||||
#### States
|
||||
|
||||
##### init (aka unknown)
|
||||
|
||||
The FSM is created in `unknown` state. When started, by the reactor (`startFSMEv`), it broadcasts Status requests and transitions to `waitForPeer` state.
|
||||
|
||||
##### waitForPeer
|
||||
|
||||
In this state, the FSM waits for a Status responses from a "tall" peer. A timer is running in this state to allow the FSM to finish if there are no useful peers.
|
||||
|
||||
If the timer expires, it moves to `finished` state and calls the reactor to switch to consensus.
|
||||
If a Status response is received from a peer within the timeout, the FSM transitions to `waitForBlock` state.
|
||||
|
||||
##### waitForBlock
|
||||
|
||||
In this state the FSM makes Block requests (triggered by a ticker in reactor) and waits for Block responses. There is a timer running in this state to detect if a peer is not sending the block at current processing height. If the timer expires, the FSM removes the peer where the request was sent and all requests made to that peer are redone.
|
||||
|
||||
As blocks are received they are stored by the pool. Block execution is independently performed by the reactor and the result reported to the FSM:
|
||||
|
||||
- if there are no errors, the FSM increases the pool height and resets the state timer.
|
||||
- if there are errors, the peers that delivered the two blocks (at height and height+1) are removed and the requests redone.
|
||||
|
||||
In this state the FSM may receive peer remove events in any of the following scenarios:
|
||||
In this state the FSM may receive peer remove events in any of the following scenarios:
|
||||
|
||||
- the switch is removing a peer
|
||||
- a peer is penalized because it has not responded to some block requests for a long time
|
||||
- a peer is penalized for being slow
|
||||
@@ -183,6 +202,7 @@ When processing of the last block (the one with height equal to the highest peer
|
||||
If after a peer update or removal the pool height is same as maxPeerHeight, the FSM transitions to `finished` state.
|
||||
|
||||
##### finished
|
||||
|
||||
When entering this state, the FSM calls the reactor to switch to consensus and performs cleanup.
|
||||
|
||||
#### Events
|
||||
@@ -191,18 +211,19 @@ The following events are handled by the FSM:
|
||||
|
||||
```go
|
||||
const (
|
||||
startFSMEv = iota + 1
|
||||
statusResponseEv
|
||||
blockResponseEv
|
||||
processedBlockEv
|
||||
makeRequestsEv
|
||||
stopFSMEv
|
||||
peerRemoveEv = iota + 256
|
||||
stateTimeoutEv
|
||||
startFSMEv = iota + 1
|
||||
statusResponseEv
|
||||
blockResponseEv
|
||||
processedBlockEv
|
||||
makeRequestsEv
|
||||
stopFSMEv
|
||||
peerRemoveEv = iota + 256
|
||||
stateTimeoutEv
|
||||
)
|
||||
```
|
||||
|
||||
### Examples of Scenarios and Termination Handling
|
||||
|
||||
A few scenarios are covered in this section together with the current/ proposed handling.
|
||||
In general, the scenarios involving faulty peers are made worse by the fact that they may quickly be re-added.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
## Blockchain Reactor v0 Modules
|
||||
# Blockchain Reactor v0 Module
|
||||
|
||||
### Blockchain Reactor
|
||||
## Blockchain Reactor
|
||||
|
||||
- coordinates the pool for syncing
|
||||
- coordinates the store for persistence
|
||||
@@ -10,35 +10,34 @@
|
||||
- starts the pool.Start() and its poolRoutine()
|
||||
- registers all the concrete types and interfaces for serialisation
|
||||
|
||||
#### poolRoutine
|
||||
### poolRoutine
|
||||
|
||||
- listens to these channels:
|
||||
- pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends
|
||||
- pool requests blocks from a specific peer by posting to requestsCh, block reactor then sends
|
||||
a &bcBlockRequestMessage for a specific height
|
||||
- pool signals timeout of a specific peer by posting to timeoutsCh
|
||||
- switchToConsensusTicker to periodically try and switch to consensus
|
||||
- trySyncTicker to periodically check if we have fallen behind and then catch-up sync
|
||||
- if there aren't any new blocks available on the pool it skips syncing
|
||||
- pool signals timeout of a specific peer by posting to timeoutsCh
|
||||
- switchToConsensusTicker to periodically try and switch to consensus
|
||||
- trySyncTicker to periodically check if we have fallen behind and then catch-up sync
|
||||
- if there aren't any new blocks available on the pool it skips syncing
|
||||
- tries to sync the app by taking downloaded blocks from the pool, gives them to the app and stores
|
||||
them on disk
|
||||
- implements Receive which is called by the switch/peer
|
||||
- calls AddBlock on the pool when it receives a new block from a peer
|
||||
- calls AddBlock on the pool when it receives a new block from a peer
|
||||
|
||||
### Block Pool
|
||||
## Block Pool
|
||||
|
||||
- responsible for downloading blocks from peers
|
||||
- makeRequestersRoutine()
|
||||
- removes timeout peers
|
||||
- starts new requesters by calling makeNextRequester()
|
||||
- removes timeout peers
|
||||
- starts new requesters by calling makeNextRequester()
|
||||
- requestRoutine():
|
||||
- picks a peer and sends the request, then blocks until:
|
||||
- pool is stopped by listening to pool.Quit
|
||||
- requester is stopped by listening to Quit
|
||||
- request is redone
|
||||
- we receive a block
|
||||
- gotBlockCh is strange
|
||||
- picks a peer and sends the request, then blocks until:
|
||||
- pool is stopped by listening to pool.Quit
|
||||
- requester is stopped by listening to Quit
|
||||
- request is redone
|
||||
- we receive a block
|
||||
- gotBlockCh is strange
|
||||
|
||||
|
||||
### Go Routines in Blockchain Reactor
|
||||
## Go Routines in Blockchain Reactor
|
||||
|
||||

|
||||
|
||||
@@ -186,7 +186,7 @@ fetchBlock(height, pool):
|
||||
mtx.Lock()
|
||||
pool.numPending++
|
||||
redo = true
|
||||
mtx.UnLock()
|
||||
mtx.UnLock()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -251,23 +251,23 @@ main(pool):
|
||||
|
||||
while true do
|
||||
select {
|
||||
upon receiving BlockRequest(Height, Peer) on pool.requestsChannel:
|
||||
try to send bcBlockRequestMessage(Height) to Peer
|
||||
upon receiving BlockRequest(Height, Peer) on pool.requestsChannel:
|
||||
try to send bcBlockRequestMessage(Height) to Peer
|
||||
|
||||
upon receiving error(peer) on errorsChannel:
|
||||
stop peer for error
|
||||
upon receiving error(peer) on errorsChannel:
|
||||
stop peer for error
|
||||
|
||||
upon receiving message on statusUpdateTickerChannel:
|
||||
broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine
|
||||
upon receiving message on statusUpdateTickerChannel:
|
||||
broadcast bcStatusRequestMessage(bcR.store.Height) // message sent in a separate routine
|
||||
|
||||
upon receiving message on switchToConsensusTickerChannel:
|
||||
pool.mtx.Lock()
|
||||
receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds
|
||||
ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight
|
||||
haveSomePeers = size of pool.peers > 0
|
||||
pool.mtx.Unlock()
|
||||
if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then
|
||||
switch to consensus mode
|
||||
upon receiving message on switchToConsensusTickerChannel:
|
||||
pool.mtx.Lock()
|
||||
receivedBlockOrTimedOut = pool.height > 0 || (time.Now() - pool.startTime) > 5 Seconds
|
||||
ourChainIsLongestAmongPeers = pool.maxPeerHeight == 0 || pool.height >= pool.maxPeerHeight
|
||||
haveSomePeers = size of pool.peers > 0
|
||||
pool.mtx.Unlock()
|
||||
if haveSomePeers && receivedBlockOrTimedOut && ourChainIsLongestAmongPeers then
|
||||
switch to consensus mode
|
||||
|
||||
upon receiving message on trySyncTickerChannel:
|
||||
for i = 0; i < 10; i++ do
|
||||
@@ -294,7 +294,7 @@ main(pool):
|
||||
redoRequestsForPeer(pool, peerId):
|
||||
for each requester in pool.requesters do
|
||||
if requester.getPeerID() == peerID
|
||||
enqueue msg on redoChannel for requester
|
||||
enqueue msg on redoChannel for requester
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
Reference in New Issue
Block a user