github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/blockchain/v0/reactor.go (about) 1 package v0 2 3 import ( 4 "fmt" 5 "reflect" 6 "time" 7 8 bc "github.com/tendermint/tendermint/blockchain" 9 "github.com/tendermint/tendermint/libs/log" 10 "github.com/tendermint/tendermint/p2p" 11 bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" 12 sm "github.com/tendermint/tendermint/state" 13 "github.com/tendermint/tendermint/store" 14 "github.com/tendermint/tendermint/types" 15 ) 16 17 const ( 18 // BlockchainChannel is a channel for blocks and status updates (`BlockStore` height) 19 BlockchainChannel = byte(0x40) 20 21 trySyncIntervalMS = 10 22 23 // stop syncing when last block's time is 24 // within this much of the system time. 25 // stopSyncingDurationMinutes = 10 26 27 // ask for best height every 10s 28 statusUpdateIntervalSeconds = 10 29 // check if we should switch to consensus reactor 30 switchToConsensusIntervalSeconds = 1 31 ) 32 33 type consensusReactor interface { 34 // for when we switch from blockchain reactor and fast sync to 35 // the consensus machine 36 SwitchToConsensus(state sm.State, skipWAL bool) 37 } 38 39 type peerError struct { 40 err error 41 peerID p2p.ID 42 } 43 44 func (e peerError) Error() string { 45 return fmt.Sprintf("error with peer %v: %s", e.peerID, e.err.Error()) 46 } 47 48 // BlockchainReactor handles long-term catchup syncing. 49 type BlockchainReactor struct { 50 p2p.BaseReactor 51 52 // immutable 53 initialState sm.State 54 55 blockExec *sm.BlockExecutor 56 store *store.BlockStore 57 pool *BlockPool 58 fastSync bool 59 60 requestsCh <-chan BlockRequest 61 errorsCh <-chan peerError 62 } 63 64 // NewBlockchainReactor returns new reactor instance. 65 func NewBlockchainReactor(state sm.State, blockExec *sm.BlockExecutor, store *store.BlockStore, 66 fastSync bool) *BlockchainReactor { 67 68 if state.LastBlockHeight != store.Height() { 69 panic(fmt.Sprintf("state (%v) and store (%v) height mismatch", state.LastBlockHeight, 70 store.Height())) 71 } 72 73 requestsCh := make(chan BlockRequest, maxTotalRequesters) 74 75 const capacity = 1000 // must be bigger than peers count 76 errorsCh := make(chan peerError, capacity) // so we don't block in #Receive#pool.AddBlock 77 78 pool := NewBlockPool( 79 store.Height()+1, 80 requestsCh, 81 errorsCh, 82 ) 83 84 bcR := &BlockchainReactor{ 85 initialState: state, 86 blockExec: blockExec, 87 store: store, 88 pool: pool, 89 fastSync: fastSync, 90 requestsCh: requestsCh, 91 errorsCh: errorsCh, 92 } 93 bcR.BaseReactor = *p2p.NewBaseReactor("BlockchainReactor", bcR) 94 return bcR 95 } 96 97 // SetLogger implements service.Service by setting the logger on reactor and pool. 98 func (bcR *BlockchainReactor) SetLogger(l log.Logger) { 99 bcR.BaseService.Logger = l 100 bcR.pool.Logger = l 101 } 102 103 // OnStart implements service.Service. 104 func (bcR *BlockchainReactor) OnStart() error { 105 if bcR.fastSync { 106 err := bcR.pool.Start() 107 if err != nil { 108 return err 109 } 110 go bcR.poolRoutine(false) 111 } 112 return nil 113 } 114 115 // SwitchToFastSync is called by the state sync reactor when switching to fast sync. 116 func (bcR *BlockchainReactor) SwitchToFastSync(state sm.State) error { 117 bcR.fastSync = true 118 bcR.initialState = state 119 120 bcR.pool.height = state.LastBlockHeight + 1 121 err := bcR.pool.Start() 122 if err != nil { 123 return err 124 } 125 go bcR.poolRoutine(true) 126 return nil 127 } 128 129 // OnStop implements service.Service. 130 func (bcR *BlockchainReactor) OnStop() { 131 if bcR.fastSync { 132 bcR.pool.Stop() 133 } 134 } 135 136 // GetChannels implements Reactor 137 func (bcR *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { 138 return []*p2p.ChannelDescriptor{ 139 { 140 ID: BlockchainChannel, 141 Priority: 10, 142 SendQueueCapacity: 1000, 143 RecvBufferCapacity: 50 * 4096, 144 RecvMessageCapacity: bc.MaxMsgSize, 145 }, 146 } 147 } 148 149 // AddPeer implements Reactor by sending our state to peer. 150 func (bcR *BlockchainReactor) AddPeer(peer p2p.Peer) { 151 msgBytes, err := bc.EncodeMsg(&bcproto.StatusResponse{ 152 Base: bcR.store.Base(), 153 Height: bcR.store.Height()}) 154 if err != nil { 155 bcR.Logger.Error("could not convert msg to protobuf", "err", err) 156 return 157 } 158 159 peer.Send(BlockchainChannel, msgBytes) 160 // it's OK if send fails. will try later in poolRoutine 161 162 // peer is added to the pool once we receive the first 163 // bcStatusResponseMessage from the peer and call pool.SetPeerRange 164 } 165 166 // RemovePeer implements Reactor by removing peer from the pool. 167 func (bcR *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { 168 bcR.pool.RemovePeer(peer.ID()) 169 } 170 171 // respondToPeer loads a block and sends it to the requesting peer, 172 // if we have it. Otherwise, we'll respond saying we don't have it. 173 func (bcR *BlockchainReactor) respondToPeer(msg *bcproto.BlockRequest, 174 src p2p.Peer) (queued bool) { 175 176 block := bcR.store.LoadBlock(msg.Height) 177 if block != nil { 178 bl, err := block.ToProto() 179 if err != nil { 180 bcR.Logger.Error("could not convert msg to protobuf", "err", err) 181 return false 182 } 183 184 msgBytes, err := bc.EncodeMsg(&bcproto.BlockResponse{Block: bl}) 185 if err != nil { 186 bcR.Logger.Error("could not marshal msg", "err", err) 187 return false 188 } 189 190 return src.TrySend(BlockchainChannel, msgBytes) 191 } 192 193 bcR.Logger.Info("Peer asking for a block we don't have", "src", src, "height", msg.Height) 194 195 msgBytes, err := bc.EncodeMsg(&bcproto.NoBlockResponse{Height: msg.Height}) 196 if err != nil { 197 bcR.Logger.Error("could not convert msg to protobuf", "err", err) 198 return false 199 } 200 201 return src.TrySend(BlockchainChannel, msgBytes) 202 } 203 204 // Receive implements Reactor by handling 4 types of messages (look below). 205 func (bcR *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { 206 msg, err := bc.DecodeMsg(msgBytes) 207 if err != nil { 208 bcR.Logger.Error("Error decoding message", "src", src, "chId", chID, "msg", msg, "err", err, "bytes", msgBytes) 209 bcR.Switch.StopPeerForError(src, err) 210 return 211 } 212 213 if err = bc.ValidateMsg(msg); err != nil { 214 bcR.Logger.Error("Peer sent us invalid msg", "peer", src, "msg", msg, "err", err) 215 bcR.Switch.StopPeerForError(src, err) 216 return 217 } 218 219 bcR.Logger.Debug("Receive", "src", src, "chID", chID, "msg", msg) 220 221 switch msg := msg.(type) { 222 case *bcproto.BlockRequest: 223 bcR.respondToPeer(msg, src) 224 case *bcproto.BlockResponse: 225 bi, err := types.BlockFromProto(msg.Block) 226 if err != nil { 227 bcR.Logger.Error("Block content is invalid", "err", err) 228 return 229 } 230 bcR.pool.AddBlock(src.ID(), bi, len(msgBytes)) 231 case *bcproto.StatusRequest: 232 // Send peer our state. 233 msgBytes, err := bc.EncodeMsg(&bcproto.StatusResponse{ 234 Height: bcR.store.Height(), 235 Base: bcR.store.Base(), 236 }) 237 if err != nil { 238 bcR.Logger.Error("could not convert msg to protobut", "err", err) 239 return 240 } 241 src.TrySend(BlockchainChannel, msgBytes) 242 case *bcproto.StatusResponse: 243 // Got a peer status. Unverified. 244 bcR.pool.SetPeerRange(src.ID(), msg.Base, msg.Height) 245 case *bcproto.NoBlockResponse: 246 bcR.Logger.Debug("Peer does not have requested block", "peer", src, "height", msg.Height) 247 default: 248 bcR.Logger.Error(fmt.Sprintf("Unknown message type %v", reflect.TypeOf(msg))) 249 } 250 } 251 252 // Handle messages from the poolReactor telling the reactor what to do. 253 // NOTE: Don't sleep in the FOR_LOOP or otherwise slow it down! 254 func (bcR *BlockchainReactor) poolRoutine(stateSynced bool) { 255 256 trySyncTicker := time.NewTicker(trySyncIntervalMS * time.Millisecond) 257 statusUpdateTicker := time.NewTicker(statusUpdateIntervalSeconds * time.Second) 258 switchToConsensusTicker := time.NewTicker(switchToConsensusIntervalSeconds * time.Second) 259 260 blocksSynced := uint64(0) 261 262 chainID := bcR.initialState.ChainID 263 state := bcR.initialState 264 265 lastHundred := time.Now() 266 lastRate := 0.0 267 268 didProcessCh := make(chan struct{}, 1) 269 270 go func() { 271 for { 272 select { 273 case <-bcR.Quit(): 274 return 275 case <-bcR.pool.Quit(): 276 return 277 case request := <-bcR.requestsCh: 278 peer := bcR.Switch.Peers().Get(request.PeerID) 279 if peer == nil { 280 continue 281 } 282 msgBytes, err := bc.EncodeMsg(&bcproto.BlockRequest{Height: request.Height}) 283 if err != nil { 284 bcR.Logger.Error("could not convert msg to proto", "err", err) 285 continue 286 } 287 288 queued := peer.TrySend(BlockchainChannel, msgBytes) 289 if !queued { 290 bcR.Logger.Debug("Send queue is full, drop block request", "peer", peer.ID(), "height", request.Height) 291 } 292 case err := <-bcR.errorsCh: 293 peer := bcR.Switch.Peers().Get(err.peerID) 294 if peer != nil { 295 bcR.Switch.StopPeerForError(peer, err) 296 } 297 298 case <-statusUpdateTicker.C: 299 // ask for status updates 300 go bcR.BroadcastStatusRequest() // nolint: errcheck 301 302 } 303 } 304 }() 305 306 FOR_LOOP: 307 for { 308 select { 309 case <-switchToConsensusTicker.C: 310 height, numPending, lenRequesters := bcR.pool.GetStatus() 311 outbound, inbound, _ := bcR.Switch.NumPeers() 312 bcR.Logger.Debug("Consensus ticker", "numPending", numPending, "total", lenRequesters, 313 "outbound", outbound, "inbound", inbound) 314 if bcR.pool.IsCaughtUp() { 315 bcR.Logger.Info("Time to switch to consensus reactor!", "height", height) 316 bcR.pool.Stop() 317 conR, ok := bcR.Switch.Reactor("CONSENSUS").(consensusReactor) 318 if ok { 319 conR.SwitchToConsensus(state, blocksSynced > 0 || stateSynced) 320 } 321 // else { 322 // should only happen during testing 323 // } 324 325 break FOR_LOOP 326 } 327 328 case <-trySyncTicker.C: // chan time 329 select { 330 case didProcessCh <- struct{}{}: 331 default: 332 } 333 334 case <-didProcessCh: 335 // NOTE: It is a subtle mistake to process more than a single block 336 // at a time (e.g. 10) here, because we only TrySend 1 request per 337 // loop. The ratio mismatch can result in starving of blocks, a 338 // sudden burst of requests and responses, and repeat. 339 // Consequently, it is better to split these routines rather than 340 // coupling them as it's written here. TODO uncouple from request 341 // routine. 342 343 // See if there are any blocks to sync. 344 first, second := bcR.pool.PeekTwoBlocks() 345 //bcR.Logger.Info("TrySync peeked", "first", first, "second", second) 346 if first == nil || second == nil { 347 // We need both to sync the first block. 348 continue FOR_LOOP 349 } else { 350 // Try again quickly next loop. 351 didProcessCh <- struct{}{} 352 } 353 354 firstParts := first.MakePartSet(types.BlockPartSizeBytes) 355 firstPartSetHeader := firstParts.Header() 356 firstID := types.BlockID{Hash: first.Hash(), PartSetHeader: firstPartSetHeader} 357 // Finally, verify the first block using the second's commit 358 // NOTE: we can probably make this more efficient, but note that calling 359 // first.Hash() doesn't verify the tx contents, so MakePartSet() is 360 // currently necessary. 361 err := state.Validators.VerifyCommit( 362 chainID, firstID, first.Height, second.LastCommit) 363 if err != nil { 364 bcR.Logger.Error("Error in validation", "err", err) 365 peerID := bcR.pool.RedoRequest(first.Height) 366 peer := bcR.Switch.Peers().Get(peerID) 367 if peer != nil { 368 // NOTE: we've already removed the peer's request, but we 369 // still need to clean up the rest. 370 bcR.Switch.StopPeerForError(peer, fmt.Errorf("blockchainReactor validation error: %v", err)) 371 } 372 peerID2 := bcR.pool.RedoRequest(second.Height) 373 peer2 := bcR.Switch.Peers().Get(peerID2) 374 if peer2 != nil && peer2 != peer { 375 // NOTE: we've already removed the peer's request, but we 376 // still need to clean up the rest. 377 bcR.Switch.StopPeerForError(peer2, fmt.Errorf("blockchainReactor validation error: %v", err)) 378 } 379 continue FOR_LOOP 380 } else { 381 bcR.pool.PopRequest() 382 383 // TODO: batch saves so we dont persist to disk every block 384 bcR.store.SaveBlock(first, firstParts, second.LastCommit) 385 386 // TODO: same thing for app - but we would need a way to 387 // get the hash without persisting the state 388 var err error 389 state, _, err = bcR.blockExec.ApplyBlock(state, firstID, first) 390 if err != nil { 391 // TODO This is bad, are we zombie? 392 panic(fmt.Sprintf("Failed to process committed block (%d:%X): %v", first.Height, first.Hash(), err)) 393 } 394 blocksSynced++ 395 396 if blocksSynced%100 == 0 { 397 lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds()) 398 bcR.Logger.Info("Fast Sync Rate", "height", bcR.pool.height, 399 "max_peer_height", bcR.pool.MaxPeerHeight(), "blocks/s", lastRate) 400 lastHundred = time.Now() 401 } 402 } 403 continue FOR_LOOP 404 405 case <-bcR.Quit(): 406 break FOR_LOOP 407 } 408 } 409 } 410 411 // BroadcastStatusRequest broadcasts `BlockStore` base and height. 412 func (bcR *BlockchainReactor) BroadcastStatusRequest() error { 413 bm, err := bc.EncodeMsg(&bcproto.StatusRequest{}) 414 if err != nil { 415 bcR.Logger.Error("could not convert msg to proto", "err", err) 416 return fmt.Errorf("could not convert msg to proto: %w", err) 417 } 418 419 bcR.Switch.Broadcast(BlockchainChannel, bm) 420 421 return nil 422 }