// handleHeadersMsghandles headers messages from all peers. func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { // The remote peer is misbehaving if we didn't request headers. msg := hmsg.headers numHeaders := len(msg.Headers) if !b.headersFirstMode { bmgrLog.Warnf("Got %d unrequested headers from %s -- "+ "disconnecting", numHeaders, hmsg.peer.addr) hmsg.peer.Disconnect() return } // Nothing to do for an empty headers message. if numHeaders == 0 { return } // Process all of the received headers ensuring each one connects to the // previous and that checkpoints match. receivedCheckpoint := false var finalHash *btcwire.ShaHash for _, blockHeader := range msg.Headers { blockHash, err := blockHeader.BlockSha() if err != nil { bmgrLog.Warnf("Failed to compute hash of header "+ "received from peer %s -- disconnecting", hmsg.peer.addr) hmsg.peer.Disconnect() return } finalHash = &blockHash // Ensure there is a previous header to compare against. prevNodeEl := b.headerList.Back() if prevNodeEl == nil { bmgrLog.Warnf("Header list does not contain a previous" + "element as expected -- disconnecting peer") hmsg.peer.Disconnect() return } // Ensure the header properly connects to the previous one and // add it to the list of headers. node := headerNode{sha: &blockHash} prevNode := prevNodeEl.Value.(*headerNode) if prevNode.sha.IsEqual(&blockHeader.PrevBlock) { node.height = prevNode.height + 1 e := b.headerList.PushBack(&node) if b.startHeader == nil { b.startHeader = e } } else { bmgrLog.Warnf("Received block header that does not"+ "properly connect to the chain from peer %s "+ "-- disconnecting", hmsg.peer.addr) hmsg.peer.Disconnect() return } // Verify the header at the next checkpoint height matches. if node.height == b.nextCheckpoint.Height { if node.sha.IsEqual(b.nextCheckpoint.Hash) { receivedCheckpoint = true bmgrLog.Infof("Verified downloaded block "+ "header against checkpoint at height "+ "%d/hash %s", node.height, node.sha) } else { bmgrLog.Warnf("Block header at height %d/hash "+ "%s from peer %s does NOT match "+ "expected checkpoint hash of %s -- "+ "disconnecting", node.height, node.sha, hmsg.peer.addr, b.nextCheckpoint.Hash) hmsg.peer.Disconnect() return } break } } // When this header is a checkpoint, switch to fetching the blocks for // all of the headers since the last checkpoint. if receivedCheckpoint { // Since the first entry of the list is always the final block // that is already in the database and is only used to ensure // the next header links properly, it must be removed before // fetching the blocks. b.headerList.Remove(b.headerList.Front()) bmgrLog.Infof("Received %v block headers: Fetching blocks", b.headerList.Len()) b.lastBlockLogTime = time.Now() b.fetchHeaderBlocks() return } // This header is not a checkpoint, so request the next batch of // headers starting from the latest known header and ending with the // next checkpoint. locator := btcchain.BlockLocator([]*btcwire.ShaHash{finalHash}) err := hmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash) if err != nil { bmgrLog.Warnf("Failed to send getheaders message to "+ "peer %s: %v", hmsg.peer.addr, err) return } }
// handleBlockMsg handles block messages from all peers. func (b *blockManager) handleBlockMsg(bmsg *blockMsg) { // If we didn't ask for this block then the peer is misbehaving. blockSha, _ := bmsg.block.Sha() if _, ok := bmsg.peer.requestedBlocks[*blockSha]; !ok { // The regression test intentionally sends some blocks twice // to test duplicate block insertion fails. Don't disconnect // the peer or ignore the block when we're in regression test // mode in this case so the chain code is actually fed the // duplicate blocks. if !cfg.RegressionTest { bmgrLog.Warnf("Got unrequested block %v from %s -- "+ "disconnecting", blockSha, bmsg.peer.addr) bmsg.peer.Disconnect() return } } // When in headers-first mode, if the block matches the hash of the // first header in the list of headers that are being fetched, it's // eligible for less validation since the headers have already been // verified to link together and are valid up to the next checkpoint. // Also, remove the list entry for all blocks except the checkpoint // since it is needed to verify the next round of headers links // properly. isCheckpointBlock := false behaviorFlags := btcchain.BFNone if b.headersFirstMode { firstNodeEl := b.headerList.Front() if firstNodeEl != nil { firstNode := firstNodeEl.Value.(*headerNode) if blockSha.IsEqual(firstNode.sha) { behaviorFlags |= btcchain.BFFastAdd if firstNode.sha.IsEqual(b.nextCheckpoint.Hash) { isCheckpointBlock = true } else { b.headerList.Remove(firstNodeEl) } } } } // Remove block from request maps. Either chain will know about it and // so we shouldn't have any more instances of trying to fetch it, or we // will fail the insert and thus we'll retry next time we get an inv. delete(bmsg.peer.requestedBlocks, *blockSha) delete(b.requestedBlocks, *blockSha) // Process the block to include validation, best chain selection, orphan // handling, etc. isOrphan, err := b.blockChain.ProcessBlock(bmsg.block, behaviorFlags) if err != nil { // When the error is a rule error, it means the block was simply // rejected as opposed to something actually going wrong, so log // it as such. Otherwise, something really did go wrong, so log // it as an actual error. if _, ok := err.(btcchain.RuleError); ok { bmgrLog.Infof("Rejected block %v from %s: %v", blockSha, bmsg.peer, err) } else { bmgrLog.Errorf("Failed to process block %v: %v", blockSha, err) } // Convert the error into an appropriate reject message and // send it. code, reason := errToRejectErr(err) bmsg.peer.PushRejectMsg(btcwire.CmdBlock, code, reason, blockSha, false) return } // Request the parents for the orphan block from the peer that sent it. if isOrphan { orphanRoot := b.blockChain.GetOrphanRoot(blockSha) locator, err := b.blockChain.LatestBlockLocator() if err != nil { bmgrLog.Warnf("Failed to get block locator for the "+ "latest block: %v", err) } else { bmsg.peer.PushGetBlocksMsg(locator, orphanRoot) } } else { // When the block is not an orphan, log information about it and // update the chain state. b.logBlockHeight(bmsg.block) // Query the db for the latest best block since the block // that was processed could be on a side chain or have caused // a reorg. newestSha, newestHeight, _ := b.server.db.NewestSha() b.updateChainState(newestSha, newestHeight) // Allow any clients performing long polling via the // getblocktemplate RPC to be notified when the new block causes // their old block template to become stale. rpcServer := b.server.rpcServer if rpcServer != nil { rpcServer.gbtWorkState.NotifyBlockConnected(blockSha) } } // Sync the db to disk. b.server.db.Sync() // Nothing more to do if we aren't in headers-first mode. if !b.headersFirstMode { return } // This is headers-first mode, so if the block is not a checkpoint // request more blocks using the header list when the request queue is // getting short. if !isCheckpointBlock { if b.startHeader != nil && len(bmsg.peer.requestedBlocks) < minInFlightBlocks { b.fetchHeaderBlocks() } return } // This is headers-first mode and the block is a checkpoint. When // there is a next checkpoint, get the next round of headers by asking // for headers starting from the block after this one up to the next // checkpoint. prevHeight := b.nextCheckpoint.Height prevHash := b.nextCheckpoint.Hash b.nextCheckpoint = b.findNextHeaderCheckpoint(prevHeight) if b.nextCheckpoint != nil { locator := btcchain.BlockLocator([]*btcwire.ShaHash{prevHash}) err := bmsg.peer.PushGetHeadersMsg(locator, b.nextCheckpoint.Hash) if err != nil { bmgrLog.Warnf("Failed to send getheaders message to "+ "peer %s: %v", bmsg.peer.addr, err) return } bmgrLog.Infof("Downloading headers for blocks %d to %d from "+ "peer %s", prevHeight+1, b.nextCheckpoint.Height, b.syncPeer.addr) return } // This is headers-first mode, the block is a checkpoint, and there are // no more checkpoints, so switch to normal mode by requesting blocks // from the block after this one up to the end of the chain (zero hash). b.headersFirstMode = false b.headerList.Init() bmgrLog.Infof("Reached the final checkpoint -- switching to normal mode") locator := btcchain.BlockLocator([]*btcwire.ShaHash{blockSha}) err = bmsg.peer.PushGetBlocksMsg(locator, &zeroHash) if err != nil { bmgrLog.Warnf("Failed to send getblocks message to peer %s: %v", bmsg.peer.addr, err) return } }