github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/blockchain/v2/reactor.go (about) 1 package v2 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 bcproto "github.com/tendermint/tendermint/proto/tendermint/blockchain" 9 10 "github.com/line/ostracon/behaviour" 11 bc "github.com/line/ostracon/blockchain" 12 "github.com/line/ostracon/libs/log" 13 tmsync "github.com/line/ostracon/libs/sync" 14 "github.com/line/ostracon/p2p" 15 ocbcproto "github.com/line/ostracon/proto/ostracon/blockchain" 16 "github.com/line/ostracon/state" 17 "github.com/line/ostracon/types" 18 ) 19 20 const ( 21 // chBufferSize is the buffer size of all event channels. 22 chBufferSize int = 1000 23 ) 24 25 type blockStore interface { 26 LoadBlock(height int64) *types.Block 27 SaveBlock(*types.Block, *types.PartSet, *types.Commit) 28 Base() int64 29 Height() int64 30 } 31 32 // BlockchainReactor handles fast sync protocol. 33 type BlockchainReactor struct { 34 p2p.BaseReactor 35 36 fastSync bool // if true, enable fast sync on start 37 stateSynced bool // set to true when SwitchToFastSync is called by state sync 38 scheduler *Routine 39 processor *Routine 40 logger log.Logger 41 42 mtx tmsync.RWMutex 43 maxPeerHeight int64 44 syncHeight int64 45 events chan Event // non-nil during a fast sync 46 47 reporter behaviour.Reporter 48 io iIO 49 store blockStore 50 } 51 52 //nolint:unused,deadcode 53 type blockVerifier interface { 54 VerifyCommit(chainID string, blockID types.BlockID, height int64, commit *types.Commit) error 55 } 56 57 type blockApplier interface { 58 ApplyBlock(state state.State, blockID types.BlockID, block *types.Block, times *state.CommitStepTimes) (state.State, 59 int64, error) 60 } 61 62 // XXX: unify naming in this package around tmState 63 func newReactor(state state.State, store blockStore, reporter behaviour.Reporter, 64 blockApplier blockApplier, fastSync bool) *BlockchainReactor { 65 initHeight := state.LastBlockHeight + 1 66 if initHeight == 1 { 67 initHeight = state.InitialHeight 68 } 69 scheduler := newScheduler(initHeight, time.Now()) 70 pContext := newProcessorContext(store, blockApplier, state) 71 // TODO: Fix naming to just newProcesssor 72 // newPcState requires a processorContext 73 processor := newPcState(pContext) 74 75 return &BlockchainReactor{ 76 scheduler: newRoutine("scheduler", scheduler.handle, chBufferSize), 77 processor: newRoutine("processor", processor.handle, chBufferSize), 78 store: store, 79 reporter: reporter, 80 logger: log.NewNopLogger(), 81 fastSync: fastSync, 82 } 83 } 84 85 // NewBlockchainReactor creates a new reactor instance. 86 func NewBlockchainReactor( 87 state state.State, 88 blockApplier blockApplier, 89 store blockStore, 90 fastSync bool) *BlockchainReactor { 91 reporter := behaviour.NewMockReporter() 92 return newReactor(state, store, reporter, blockApplier, fastSync) 93 } 94 95 // SetSwitch implements Reactor interface. 96 func (r *BlockchainReactor) SetSwitch(sw *p2p.Switch) { 97 r.Switch = sw 98 if sw != nil { 99 r.io = newSwitchIo(sw) 100 } else { 101 r.io = nil 102 } 103 } 104 105 func (r *BlockchainReactor) setMaxPeerHeight(height int64) { 106 r.mtx.Lock() 107 defer r.mtx.Unlock() 108 if height > r.maxPeerHeight { 109 r.maxPeerHeight = height 110 } 111 } 112 113 func (r *BlockchainReactor) setSyncHeight(height int64) { 114 r.mtx.Lock() 115 defer r.mtx.Unlock() 116 r.syncHeight = height 117 } 118 119 // SyncHeight returns the height to which the BlockchainReactor has synced. 120 func (r *BlockchainReactor) SyncHeight() int64 { 121 r.mtx.RLock() 122 defer r.mtx.RUnlock() 123 return r.syncHeight 124 } 125 126 // SetLogger sets the logger of the reactor. 127 func (r *BlockchainReactor) SetLogger(logger log.Logger) { 128 r.logger = logger 129 r.scheduler.setLogger(logger) 130 r.processor.setLogger(logger) 131 } 132 133 // Start implements cmn.Service interface 134 func (r *BlockchainReactor) Start() error { 135 r.reporter = behaviour.NewSwitchReporter(r.BaseReactor.Switch) 136 if r.fastSync { 137 err := r.startSync(nil) 138 if err != nil { 139 return fmt.Errorf("failed to start fast sync: %w", err) 140 } 141 } 142 return nil 143 } 144 145 // startSync begins a fast sync, signalled by r.events being non-nil. If state is non-nil, 146 // the scheduler and processor is updated with this state on startup. 147 func (r *BlockchainReactor) startSync(state *state.State) error { 148 r.mtx.Lock() 149 defer r.mtx.Unlock() 150 if r.events != nil { 151 return errors.New("fast sync already in progress") 152 } 153 r.events = make(chan Event, chBufferSize) 154 go r.scheduler.start() 155 go r.processor.start() 156 if state != nil { 157 <-r.scheduler.ready() 158 <-r.processor.ready() 159 r.scheduler.send(bcResetState{state: *state}) 160 r.processor.send(bcResetState{state: *state}) 161 } 162 go r.demux(r.events) 163 return nil 164 } 165 166 // endSync ends a fast sync 167 func (r *BlockchainReactor) endSync() { 168 r.mtx.Lock() 169 defer r.mtx.Unlock() 170 if r.events != nil { 171 close(r.events) 172 } 173 r.events = nil 174 r.scheduler.stop() 175 r.processor.stop() 176 } 177 178 // SwitchToFastSync is called by the state sync reactor when switching to fast sync. 179 func (r *BlockchainReactor) SwitchToFastSync(state state.State) error { 180 r.stateSynced = true 181 state = state.Copy() 182 return r.startSync(&state) 183 } 184 185 // reactor generated ticker events: 186 // ticker for cleaning peers 187 type rTryPrunePeer struct { 188 priorityHigh 189 time time.Time 190 } 191 192 func (e rTryPrunePeer) String() string { 193 return fmt.Sprintf("rTryPrunePeer{%v}", e.time) 194 } 195 196 // ticker event for scheduling block requests 197 type rTrySchedule struct { 198 priorityHigh 199 time time.Time 200 } 201 202 func (e rTrySchedule) String() string { 203 return fmt.Sprintf("rTrySchedule{%v}", e.time) 204 } 205 206 // ticker for block processing 207 type rProcessBlock struct { 208 priorityNormal 209 } 210 211 func (e rProcessBlock) String() string { 212 return "rProcessBlock" 213 } 214 215 // reactor generated events based on blockchain related messages from peers: 216 // blockResponse message received from a peer 217 type bcBlockResponse struct { 218 priorityNormal 219 time time.Time 220 peerID p2p.ID 221 size int64 222 block *types.Block 223 } 224 225 func (resp bcBlockResponse) String() string { 226 return fmt.Sprintf("bcBlockResponse{%d#%X (size: %d bytes) from %v at %v}", 227 resp.block.Height, resp.block.Hash(), resp.size, resp.peerID, resp.time) 228 } 229 230 // blockNoResponse message received from a peer 231 type bcNoBlockResponse struct { 232 priorityNormal 233 time time.Time 234 peerID p2p.ID 235 height int64 236 } 237 238 func (resp bcNoBlockResponse) String() string { 239 return fmt.Sprintf("bcNoBlockResponse{%v has no block at height %d at %v}", 240 resp.peerID, resp.height, resp.time) 241 } 242 243 // statusResponse message received from a peer 244 type bcStatusResponse struct { 245 priorityNormal 246 time time.Time 247 peerID p2p.ID 248 base int64 249 height int64 250 } 251 252 func (resp bcStatusResponse) String() string { 253 return fmt.Sprintf("bcStatusResponse{%v is at height %d (base: %d) at %v}", 254 resp.peerID, resp.height, resp.base, resp.time) 255 } 256 257 // new peer is connected 258 type bcAddNewPeer struct { 259 priorityNormal 260 peerID p2p.ID 261 } 262 263 func (resp bcAddNewPeer) String() string { 264 return fmt.Sprintf("bcAddNewPeer{%v}", resp.peerID) 265 } 266 267 // existing peer is removed 268 type bcRemovePeer struct { 269 priorityHigh 270 peerID p2p.ID 271 reason interface{} 272 } 273 274 func (resp bcRemovePeer) String() string { 275 return fmt.Sprintf("bcRemovePeer{%v due to %v}", resp.peerID, resp.reason) 276 } 277 278 // resets the scheduler and processor state, e.g. following a switch from state syncing 279 type bcResetState struct { 280 priorityHigh 281 state state.State 282 } 283 284 func (e bcResetState) String() string { 285 return fmt.Sprintf("bcResetState{%v}", e.state) 286 } 287 288 // Takes the channel as a parameter to avoid race conditions on r.events. 289 func (r *BlockchainReactor) demux(events <-chan Event) { 290 var lastRate = 0.0 291 var lastHundred = time.Now() 292 293 var ( 294 processBlockFreq = 20 * time.Millisecond 295 doProcessBlockCh = make(chan struct{}, 1) 296 doProcessBlockTk = time.NewTicker(processBlockFreq) 297 ) 298 defer doProcessBlockTk.Stop() 299 300 var ( 301 prunePeerFreq = 1 * time.Second 302 doPrunePeerCh = make(chan struct{}, 1) 303 doPrunePeerTk = time.NewTicker(prunePeerFreq) 304 ) 305 defer doPrunePeerTk.Stop() 306 307 var ( 308 scheduleFreq = 20 * time.Millisecond 309 doScheduleCh = make(chan struct{}, 1) 310 doScheduleTk = time.NewTicker(scheduleFreq) 311 ) 312 defer doScheduleTk.Stop() 313 314 var ( 315 statusFreq = 10 * time.Second 316 doStatusCh = make(chan struct{}, 1) 317 doStatusTk = time.NewTicker(statusFreq) 318 ) 319 defer doStatusTk.Stop() 320 doStatusCh <- struct{}{} // immediately broadcast to get status of existing peers 321 322 // XXX: Extract timers to make testing atemporal 323 for { 324 select { 325 // Pacers: send at most per frequency but don't saturate 326 case <-doProcessBlockTk.C: 327 select { 328 case doProcessBlockCh <- struct{}{}: 329 default: 330 } 331 case <-doPrunePeerTk.C: 332 select { 333 case doPrunePeerCh <- struct{}{}: 334 default: 335 } 336 case <-doScheduleTk.C: 337 select { 338 case doScheduleCh <- struct{}{}: 339 default: 340 } 341 case <-doStatusTk.C: 342 select { 343 case doStatusCh <- struct{}{}: 344 default: 345 } 346 347 // Tickers: perform tasks periodically 348 case <-doScheduleCh: 349 r.scheduler.send(rTrySchedule{time: time.Now()}) 350 case <-doPrunePeerCh: 351 r.scheduler.send(rTryPrunePeer{time: time.Now()}) 352 case <-doProcessBlockCh: 353 r.processor.send(rProcessBlock{}) 354 case <-doStatusCh: 355 if err := r.io.broadcastStatusRequest(); err != nil { 356 r.logger.Error("Error broadcasting status request", "err", err) 357 } 358 359 // Events from peers. Closing the channel signals event loop termination. 360 case event, ok := <-events: 361 if !ok { 362 r.logger.Info("Stopping event processing") 363 return 364 } 365 switch event := event.(type) { 366 case bcStatusResponse: 367 r.setMaxPeerHeight(event.height) 368 r.scheduler.send(event) 369 case bcAddNewPeer, bcRemovePeer, bcBlockResponse, bcNoBlockResponse: 370 r.scheduler.send(event) 371 default: 372 r.logger.Error("Received unexpected event", "event", fmt.Sprintf("%T", event)) 373 } 374 375 // Incremental events from scheduler 376 case event := <-r.scheduler.next(): 377 switch event := event.(type) { 378 case scBlockReceived: 379 r.processor.send(event) 380 case scPeerError: 381 r.processor.send(event) 382 if err := r.reporter.Report(behaviour.BadMessage(event.peerID, "scPeerError")); err != nil { 383 r.logger.Error("Error reporting peer", "err", err) 384 } 385 case scBlockRequest: 386 if err := r.io.sendBlockRequest(event.peerID, event.height); err != nil { 387 r.logger.Error("Error sending block request", "err", err) 388 } 389 case scFinishedEv: 390 r.processor.send(event) 391 r.scheduler.stop() 392 case scSchedulerFail: 393 r.logger.Error("Scheduler failure", "err", event.reason.Error()) 394 case scPeersPruned: 395 // Remove peers from the processor. 396 for _, peerID := range event.peers { 397 r.processor.send(scPeerError{peerID: peerID, reason: errors.New("peer was pruned")}) 398 } 399 r.logger.Debug("Pruned peers", "count", len(event.peers)) 400 case noOpEvent: 401 default: 402 r.logger.Error("Received unexpected scheduler event", "event", fmt.Sprintf("%T", event)) 403 } 404 405 // Incremental events from processor 406 case event := <-r.processor.next(): 407 switch event := event.(type) { 408 case pcBlockProcessed: 409 r.setSyncHeight(event.height) 410 if r.syncHeight%100 == 0 { 411 lastRate = 0.9*lastRate + 0.1*(100/time.Since(lastHundred).Seconds()) 412 r.logger.Info("Fast Sync Rate", "height", r.syncHeight, 413 "max_peer_height", r.maxPeerHeight, "blocks/s", lastRate) 414 lastHundred = time.Now() 415 } 416 r.scheduler.send(event) 417 case pcBlockVerificationFailure: 418 r.scheduler.send(event) 419 case pcFinished: 420 r.logger.Info("Fast sync complete, switching to consensus") 421 if !r.io.trySwitchToConsensus(event.tmState, event.blocksSynced > 0 || r.stateSynced) { 422 r.logger.Error("Failed to switch to consensus reactor") 423 } 424 r.endSync() 425 return 426 case noOpEvent: 427 default: 428 r.logger.Error("Received unexpected processor event", "event", fmt.Sprintf("%T", event)) 429 } 430 431 // Terminal event from scheduler 432 case err := <-r.scheduler.final(): 433 switch err { 434 case nil: 435 r.logger.Info("Scheduler stopped") 436 default: 437 r.logger.Error("Scheduler aborted with error", "err", err) 438 } 439 440 // Terminal event from processor 441 case err := <-r.processor.final(): 442 switch err { 443 case nil: 444 r.logger.Info("Processor stopped") 445 default: 446 r.logger.Error("Processor aborted with error", "err", err) 447 } 448 } 449 } 450 } 451 452 // Stop implements cmn.Service interface. 453 func (r *BlockchainReactor) Stop() error { 454 r.logger.Info("reactor stopping") 455 r.endSync() 456 r.logger.Info("reactor stopped") 457 return nil 458 } 459 460 // Receive implements Reactor by handling different message types. 461 func (r *BlockchainReactor) Receive(chID byte, src p2p.Peer, msgBytes []byte) { 462 msg, err := bc.DecodeMsg(msgBytes) 463 if err != nil { 464 r.logger.Error("error decoding message", 465 "src", src.ID(), "chId", chID, "msg", msg, "err", err) 466 _ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error())) 467 return 468 } 469 470 if err = bc.ValidateMsg(msg); err != nil { 471 r.logger.Error("peer sent us invalid msg", "peer", src, "msg", msg, "err", err) 472 _ = r.reporter.Report(behaviour.BadMessage(src.ID(), err.Error())) 473 return 474 } 475 476 r.logger.Debug("Receive", "src", src.ID(), "chID", chID, "msg", msg) 477 478 switch msg := msg.(type) { 479 case *bcproto.StatusRequest: 480 if err := r.io.sendStatusResponse(r.store.Base(), r.store.Height(), src.ID()); err != nil { 481 r.logger.Error("Could not send status message to peer", "src", src) 482 } 483 484 case *bcproto.BlockRequest: 485 block := r.store.LoadBlock(msg.Height) 486 if block != nil { 487 if err = r.io.sendBlockToPeer(block, src.ID()); err != nil { 488 r.logger.Error("Could not send block message to peer: ", err) 489 } 490 } else { 491 r.logger.Info("peer asking for a block we don't have", "src", src, "height", msg.Height) 492 peerID := src.ID() 493 if err = r.io.sendBlockNotFound(msg.Height, peerID); err != nil { 494 r.logger.Error("Couldn't send block not found: ", err) 495 } 496 } 497 498 case *bcproto.StatusResponse: 499 r.mtx.RLock() 500 if r.events != nil { 501 r.events <- bcStatusResponse{peerID: src.ID(), base: msg.Base, height: msg.Height} 502 } 503 r.mtx.RUnlock() 504 505 case *ocbcproto.BlockResponse: 506 bi, err := types.BlockFromProto(msg.Block) 507 if err != nil { 508 r.logger.Error("error transitioning block from protobuf", "err", err) 509 return 510 } 511 r.mtx.RLock() 512 if r.events != nil { 513 r.events <- bcBlockResponse{ 514 peerID: src.ID(), 515 block: bi, 516 size: int64(len(msgBytes)), 517 time: time.Now(), 518 } 519 } 520 r.mtx.RUnlock() 521 522 case *bcproto.NoBlockResponse: 523 r.mtx.RLock() 524 if r.events != nil { 525 r.events <- bcNoBlockResponse{peerID: src.ID(), height: msg.Height, time: time.Now()} 526 } 527 r.mtx.RUnlock() 528 } 529 } 530 531 // AddPeer implements Reactor interface 532 func (r *BlockchainReactor) AddPeer(peer p2p.Peer) { 533 err := r.io.sendStatusResponse(r.store.Base(), r.store.Height(), peer.ID()) 534 if err != nil { 535 r.logger.Error("Could not send status message to peer new", "src", peer.ID, "height", r.SyncHeight()) 536 } 537 r.mtx.RLock() 538 defer r.mtx.RUnlock() 539 if r.events != nil { 540 r.events <- bcAddNewPeer{peerID: peer.ID()} 541 } 542 } 543 544 // RemovePeer implements Reactor interface. 545 func (r *BlockchainReactor) RemovePeer(peer p2p.Peer, reason interface{}) { 546 r.mtx.RLock() 547 defer r.mtx.RUnlock() 548 if r.events != nil { 549 r.events <- bcRemovePeer{ 550 peerID: peer.ID(), 551 reason: reason, 552 } 553 } 554 } 555 556 // GetChannels implements Reactor 557 func (r *BlockchainReactor) GetChannels() []*p2p.ChannelDescriptor { 558 return []*p2p.ChannelDescriptor{ 559 { 560 ID: BlockchainChannel, 561 Priority: 5, 562 SendQueueCapacity: 2000, 563 RecvBufferCapacity: 50 * 4096, 564 RecvMessageCapacity: bc.MaxMsgSize, 565 }, 566 } 567 }