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