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