// NOTE: THIS API IS UNSTABLE RIGHT NOW AND WILL GO MOSTLY PRIVATE SOON. package neutrino import ( "bytes" "container/list" "fmt" "math" "math/big" "sync" "sync/atomic" "time" "github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil/gcs" "github.com/btcsuite/btcutil/gcs/builder" "github.com/lightninglabs/neutrino/banman" "github.com/lightninglabs/neutrino/blockntfns" "github.com/lightninglabs/neutrino/chainsync" "github.com/lightninglabs/neutrino/headerfs" "github.com/lightninglabs/neutrino/headerlist" ) const ( // maxTimeOffset is the maximum duration a block time is allowed to be // ahead of the curent time. This is currently 2 hours. maxTimeOffset = 2 * time.Hour // numMaxMemHeaders is the max number of headers to store in memory for // a particular peer. By bounding this value, we're able to closely // control our effective memory usage during initial sync and re-org // handling. This value should be set a "sane" re-org size, such that // we're able to properly handle re-orgs in size strictly less than // this value. numMaxMemHeaders = 10000 // retryTimeout is the time we'll wait between failed queries to fetch // filter checkpoints and headers. retryTimeout = 3 * time.Second // maxCFCheckptsPerQuery is the maximum number of filter header // checkpoints we can query for within a single message over the wire. maxCFCheckptsPerQuery = wire.MaxCFHeadersPerMsg / wire.CFCheckptInterval ) // filterStoreLookup type filterStoreLookup func(*ChainService) *headerfs.FilterHeaderStore var ( // filterTypes is a map of filter types to synchronize to a lookup // function for the service's store for that filter type. filterTypes = map[wire.FilterType]filterStoreLookup{ wire.GCSFilterRegular: func( s *ChainService) *headerfs.FilterHeaderStore { return s.RegFilterHeaders }, } ) // zeroHash is the zero value hash (all zeros). It is defined as a convenience. var zeroHash chainhash.Hash // newPeerMsg signifies a newly connected peer to the block handler. type newPeerMsg struct { peer *ServerPeer } // invMsg packages a bitcoin inv message and the peer it came from together // so the block handler has access to that information. type invMsg struct { inv *wire.MsgInv peer *ServerPeer } // headersMsg packages a bitcoin headers message and the peer it came from // together so the block handler has access to that information. type headersMsg struct { headers *wire.MsgHeaders peer *ServerPeer } // donePeerMsg signifies a newly disconnected peer to the block handler. type donePeerMsg struct { peer *ServerPeer } // txMsg packages a bitcoin tx message and the peer it came from together // so the block handler has access to that information. type txMsg struct { tx *btcutil.Tx peer *ServerPeer } // blockManager provides a concurrency safe block manager for handling all // incoming blocks. type blockManager struct { started int32 shutdown int32 // blkHeaderProgressLogger is a progress logger that we'll use to // update the number of blocker headers we've processed in the past 10 // seconds within the log. blkHeaderProgressLogger *headerProgressLogger // fltrHeaderProgessLogger is a process logger similar to the one // above, but we'll use it to update the progress of the set of filter // headers that we've verified in the past 10 seconds. fltrHeaderProgessLogger *headerProgressLogger // genesisHeader is the filter header of the genesis block. genesisHeader chainhash.Hash // headerTip will be set to the current block header tip at all times. // Callers MUST hold the lock below each time they read/write from // this field. headerTip uint32 // headerTipHash will be set to the hash of the current block header // tip at all times. Callers MUST hold the lock below each time they // read/write from this field. headerTipHash chainhash.Hash // newHeadersMtx is the mutex that should be held when reading/writing // the headerTip variable above. // // NOTE: When using this mutex along with newFilterHeadersMtx at the // same time, newHeadersMtx should always be acquired first. newHeadersMtx sync.RWMutex // newHeadersSignal is condition variable which will be used to notify // any waiting callers (via Broadcast()) that the tip of the current // chain has changed. This is useful when callers need to know we have // a new tip, but not necessarily each block that was connected during // switch over. newHeadersSignal *sync.Cond // filterHeaderTip will be set to the height of the current filter // header tip at all times. Callers MUST hold the lock below each time // they read/write from this field. filterHeaderTip uint32 // filterHeaderTipHash will be set to the current block hash of the // block at height filterHeaderTip at all times. Callers MUST hold the // lock below each time they read/write from this field. filterHeaderTipHash chainhash.Hash // newFilterHeadersMtx is the mutex that should be held when // reading/writing the filterHeaderTip variable above. // // NOTE: When using this mutex along with newHeadersMtx at the same // time, newHeadersMtx should always be acquired first. newFilterHeadersMtx sync.RWMutex // newFilterHeadersSignal is condition variable which will be used to // notify any waiting callers (via Broadcast()) that the tip of the // current filter header chain has changed. This is useful when callers // need to know we have a new tip, but not necessarily each filter // header that was connected during switch over. newFilterHeadersSignal *sync.Cond // syncPeer points to the peer that we're currently syncing block // headers from. syncPeer *ServerPeer // syncPeerMutex protects the above syncPeer pointer at all times. syncPeerMutex sync.RWMutex // server is a pointer to the main p2p server for Neutrino, we'll use // this pointer at times to do things like access the database, etc // TODO(halseth): replace with ChainSource interface to ease unit // testing. server *ChainService // queries is an interface allowing querying peers. queries QueryAccess // peerChan is a channel for messages that come from peers peerChan chan interface{} // firstPeerSignal is a channel that's sent upon once the main daemon // has made its first peer connection. We use this to ensure we don't // try to perform any queries before we have our first peer. firstPeerSignal <-chan struct{} // blockNtfnChan is a channel in which the latest block notifications // for the tip of the chain will be sent upon. blockNtfnChan chan blockntfns.BlockNtfn wg sync.WaitGroup quit chan struct{} headerList headerlist.Chain reorgList headerlist.Chain startHeader *headerlist.Node nextCheckpoint *chaincfg.Checkpoint lastRequested chainhash.Hash minRetargetTimespan int64 // target timespan / adjustment factor maxRetargetTimespan int64 // target timespan * adjustment factor blocksPerRetarget int32 // target timespan / target time per block } // newBlockManager returns a new bitcoin block manager. Use Start to begin // processing asynchronous block and inv updates. func newBlockManager(s *ChainService, firstPeerSignal <-chan struct{}) (*blockManager, error) { targetTimespan := int64(s.chainParams.TargetTimespan / time.Second) targetTimePerBlock := int64(s.chainParams.TargetTimePerBlock / time.Second) adjustmentFactor := s.chainParams.RetargetAdjustmentFactor bm := blockManager{ server: s, queries: s, peerChan: make(chan interface{}, MaxPeers*3), blockNtfnChan: make(chan blockntfns.BlockNtfn), blkHeaderProgressLogger: newBlockProgressLogger( "Processed", "block", log, ), fltrHeaderProgessLogger: newBlockProgressLogger( "Verified", "filter header", log, ), headerList: headerlist.NewBoundedMemoryChain( numMaxMemHeaders, ), reorgList: headerlist.NewBoundedMemoryChain( numMaxMemHeaders, ), quit: make(chan struct{}), blocksPerRetarget: int32(targetTimespan / targetTimePerBlock), minRetargetTimespan: targetTimespan / adjustmentFactor, maxRetargetTimespan: targetTimespan * adjustmentFactor, firstPeerSignal: firstPeerSignal, } // Next we'll create the two signals that goroutines will use to wait // on a particular header chain height before starting their normal // duties. bm.newHeadersSignal = sync.NewCond(&bm.newHeadersMtx) bm.newFilterHeadersSignal = sync.NewCond(&bm.newFilterHeadersMtx) // We fetch the genesis header to use for verifying the first received // interval. genesisHeader, err := s.RegFilterHeaders.FetchHeaderByHeight(0) if err != nil { return nil, err } bm.genesisHeader = *genesisHeader // Initialize the next checkpoint based on the current height. header, height, err := s.BlockHeaders.ChainTip() if err != nil { return nil, err } bm.nextCheckpoint = bm.findNextHeaderCheckpoint(int32(height)) bm.headerList.ResetHeaderState(headerlist.Node{ Header: *header, Height: int32(height), }) bm.headerTip = height bm.headerTipHash = header.BlockHash() // Finally, we'll set the filter header tip so any goroutines waiting // on the condition obtain the correct initial state. _, bm.filterHeaderTip, err = s.RegFilterHeaders.ChainTip() if err != nil { return nil, err } // We must also ensure the the filter header tip hash is set to the // block hash at the filter tip height. fh, err := s.BlockHeaders.FetchHeaderByHeight(bm.filterHeaderTip) if err != nil { return nil, err } bm.filterHeaderTipHash = fh.BlockHash() return &bm, nil } // Start begins the core block handler which processes block and inv messages. func (b *blockManager) Start() { // Already started? if atomic.AddInt32(&b.started, 1) != 1 { return } log.Trace("Starting block manager") b.wg.Add(2) go b.blockHandler() go func() { defer b.wg.Done() log.Debug("Waiting for peer connection...") // Before starting the cfHandler we want to make sure we are // connected with at least one peer. select { case <-b.firstPeerSignal: case <-b.quit: return } log.Debug("Peer connected, starting cfHandler.") b.cfHandler() }() } // Stop gracefully shuts down the block manager by stopping all asynchronous // handlers and waiting for them to finish. func (b *blockManager) Stop() error { if atomic.AddInt32(&b.shutdown, 1) != 1 { log.Warnf("Block manager is already in the process of " + "shutting down") return nil } // We'll send out update signals before the quit to ensure that any // goroutines waiting on them will properly exit. done := make(chan struct{}) go func() { ticker := time.NewTicker(time.Millisecond * 50) defer ticker.Stop() for { select { case <-done: return case <-ticker.C: } b.newHeadersSignal.Broadcast() b.newFilterHeadersSignal.Broadcast() } }() log.Infof("Block manager shutting down") close(b.quit) b.wg.Wait() close(done) return nil } // NewPeer informs the block manager of a newly active peer. func (b *blockManager) NewPeer(sp *ServerPeer) { // Ignore if we are shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } select { case b.peerChan <- &newPeerMsg{peer: sp}: case <-b.quit: return } } // handleNewPeerMsg deals with new peers that have signalled they may be // considered as a sync peer (they have already successfully negotiated). It // also starts syncing if needed. It is invoked from the syncHandler // goroutine. func (b *blockManager) handleNewPeerMsg(peers *list.List, sp *ServerPeer) { // Ignore if in the process of shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } log.Infof("New valid peer %s (%s)", sp, sp.UserAgent()) // Ignore the peer if it's not a sync candidate. if !b.isSyncCandidate(sp) { return } // Add the peer as a candidate to sync from. peers.PushBack(sp) // If we're current with our sync peer and the new peer is advertising // a higher block than the newest one we know of, request headers from // the new peer. _, height, err := b.server.BlockHeaders.ChainTip() if err != nil { log.Criticalf("Couldn't retrieve block header chain tip: %s", err) return } if height < uint32(sp.StartingHeight()) && b.BlockHeadersSynced() { locator, err := b.server.BlockHeaders.LatestBlockLocator() if err != nil { log.Criticalf("Couldn't retrieve latest block "+ "locator: %s", err) return } stopHash := &zeroHash sp.PushGetHeadersMsg(locator, stopHash) } // Start syncing by choosing the best candidate if needed. b.startSync(peers) } // DonePeer informs the blockmanager that a peer has disconnected. func (b *blockManager) DonePeer(sp *ServerPeer) { // Ignore if we are shutting down. if atomic.LoadInt32(&b.shutdown) != 0 { return } select { case b.peerChan <- &donePeerMsg{peer: sp}: case <-b.quit: return } } // handleDonePeerMsg deals with peers that have signalled they are done. It // removes the peer as a candidate for syncing and in the case where it was the // current sync peer, attempts to select a new best peer to sync from. It is // invoked from the syncHandler goroutine. func (b *blockManager) handleDonePeerMsg(peers *list.List, sp *ServerPeer) { // Remove the peer from the list of candidate peers. for e := peers.Front(); e != nil; e = e.Next() { if e.Value == sp { peers.Remove(e) break } } log.Infof("Lost peer %s", sp) // Attempt to find a new peer to sync from if the quitting peer is the // sync peer. Also, reset the header state. if b.SyncPeer() != nil && b.SyncPeer() == sp { b.syncPeerMutex.Lock() b.syncPeer = nil b.syncPeerMutex.Unlock() header, height, err := b.server.BlockHeaders.ChainTip() if err != nil { return } b.headerList.ResetHeaderState(headerlist.Node{ Header: *header, Height: int32(height), }) b.startSync(peers) } } // cfHandler is the cfheader download handler for the block manager. It must be // run as a goroutine. It requests and processes cfheaders messages in a // separate goroutine from the peer handlers. func (b *blockManager) cfHandler() { defer log.Trace("Committed filter header handler done") var ( // allCFCheckpoints is a map from our peers to the list of // filter checkpoints they respond to us with. We'll attempt to // get filter checkpoints immediately up to the latest block // checkpoint we've got stored to avoid doing unnecessary // fetches as the block headers are catching up. allCFCheckpoints map[string][]*chainhash.Hash // lastCp will point to the latest block checkpoint we have for // the active chain, if any. lastCp chaincfg.Checkpoint // blockCheckpoints is the list of block checkpoints for the // active chain. blockCheckpoints = b.server.chainParams.Checkpoints ) // Set the variable to the latest block checkpoint if we have any for // this chain. Otherwise this block checkpoint will just stay at height // 0, which will prompt us to look at the block headers to fetch // checkpoints below. if len(blockCheckpoints) > 0 { lastCp = blockCheckpoints[len(blockCheckpoints)-1] } waitForHeaders: // We'll wait until the main header sync is either finished or the // filter headers are lagging at least a checkpoint interval behind the // block headers, before we actually start to sync the set of // cfheaders. We do this to speed up the sync, as the check pointed // sync is faster, than fetching each header from each peer during the // normal "at tip" syncing. log.Infof("Waiting for more block headers, then will start "+ "cfheaders sync from height %v...", b.filterHeaderTip) b.newHeadersSignal.L.Lock() b.newFilterHeadersMtx.RLock() for !(b.filterHeaderTip+wire.CFCheckptInterval <= b.headerTip || b.BlockHeadersSynced()) { b.newFilterHeadersMtx.RUnlock() b.newHeadersSignal.Wait() // While we're awake, we'll quickly check to see if we need to // quit early. select { case <-b.quit: b.newHeadersSignal.L.Unlock() return default: } // Re-acquire the lock in order to check for the filter header // tip at the next iteration of the loop. b.newFilterHeadersMtx.RLock() } b.newFilterHeadersMtx.RUnlock() b.newHeadersSignal.L.Unlock() // Now that the block headers are finished or ahead of the filter // headers, we'll grab the current chain tip so we can base our filter // header sync off of that. lastHeader, lastHeight, err := b.server.BlockHeaders.ChainTip() if err != nil { log.Critical(err) return } lastHash := lastHeader.BlockHash() b.newFilterHeadersMtx.RLock() log.Infof("Starting cfheaders sync from (block_height=%v, "+ "block_hash=%v) to (block_height=%v, block_hash=%v)", b.filterHeaderTip, b.filterHeaderTipHash, lastHeight, lastHeader.BlockHash()) b.newFilterHeadersMtx.RUnlock() fType := wire.GCSFilterRegular store := b.server.RegFilterHeaders log.Infof("Starting cfheaders sync for filter_type=%v", fType) // If we have less than a full checkpoint's worth of blocks, such as on // simnet, we don't really need to request checkpoints as we'll get 0 // from all peers. We can go on and just request the cfheaders. var goodCheckpoints []*chainhash.Hash for len(goodCheckpoints) == 0 && lastHeight >= wire.CFCheckptInterval { // Quit if requested. select { case <-b.quit: return default: } // If the height now exceeds the height at which we fetched the // checkpoints last time, we must query our peers again. if minCheckpointHeight(allCFCheckpoints) < lastHeight { // Start by getting the filter checkpoints up to the // height of our block header chain. If we have a chain // checkpoint that is past this height, we use that // instead. We do this so we don't have to fetch all // filter checkpoints each time our block header chain // advances. // TODO(halseth): fetch filter checkpoints up to the // best block of the connected peers. bestHeight := lastHeight bestHash := lastHash if bestHeight < uint32(lastCp.Height) { bestHeight = uint32(lastCp.Height) bestHash = *lastCp.Hash } log.Debugf("Getting filter checkpoints up to "+ "height=%v, hash=%v", bestHeight, bestHash) allCFCheckpoints = b.getCheckpts(&bestHash, fType) if len(allCFCheckpoints) == 0 { log.Warnf("Unable to fetch set of " + "candidate checkpoints, trying again...") select { case <-time.After(retryTimeout): case <-b.quit: return } continue } } // Cap the received checkpoints at the current height, as we // can only verify checkpoints up to the height we have block // headers for. checkpoints := make(map[string][]*chainhash.Hash) for p, cps := range allCFCheckpoints { for i, cp := range cps { height := uint32(i+1) * wire.CFCheckptInterval if height > lastHeight { break } checkpoints[p] = append(checkpoints[p], cp) } } // See if we can detect which checkpoint list is correct. If // not, we will cycle again. goodCheckpoints, err = b.resolveConflict( checkpoints, store, fType, ) if err != nil { log.Warnf("got error attempting to determine correct "+ "cfheader checkpoints: %v, trying again", err) } if len(goodCheckpoints) == 0 { select { case <-time.After(retryTimeout): case <-b.quit: return } } } // Get all the headers up to the last known good checkpoint. b.getCheckpointedCFHeaders( goodCheckpoints, store, fType, ) // Now we check the headers again. If the block headers are not yet // current, then we go back to the loop waiting for them to finish. if !b.BlockHeadersSynced() { goto waitForHeaders } // If block headers are current, but the filter header tip is still // lagging more than a checkpoint interval behind the block header tip, // we also go back to the loop to utilize the faster check pointed // fetching. b.newHeadersMtx.RLock() b.newFilterHeadersMtx.RLock() if b.filterHeaderTip+wire.CFCheckptInterval <= b.headerTip { b.newFilterHeadersMtx.RUnlock() b.newHeadersMtx.RUnlock() goto waitForHeaders } b.newFilterHeadersMtx.RUnlock() b.newHeadersMtx.RUnlock() log.Infof("Fully caught up with cfheaders at height "+ "%v, waiting at tip for new blocks", lastHeight) // Now that we've been fully caught up to the tip of the current header // chain, we'll wait here for a signal that more blocks have been // connected. If this happens then we'll do another round to fetch the // new set of filter new set of filter headers for { // We'll wait until the filter header tip and the header tip // are mismatched. b.newHeadersSignal.L.Lock() b.newFilterHeadersMtx.RLock() for b.filterHeaderTipHash == b.headerTipHash { // We'll wait here until we're woken up by the // broadcast signal. b.newFilterHeadersMtx.RUnlock() b.newHeadersSignal.Wait() // Before we proceed, we'll check if we need to exit at // all. select { case <-b.quit: b.newHeadersSignal.L.Unlock() return default: } // Re-acquire the lock in order to check for the filter // header tip at the next iteration of the loop. b.newFilterHeadersMtx.RLock() } b.newFilterHeadersMtx.RUnlock() b.newHeadersSignal.L.Unlock() // At this point, we know that there're a set of new filter // headers to fetch, so we'll grab them now. if err = b.getUncheckpointedCFHeaders( store, fType, ); err != nil { log.Debugf("couldn't get uncheckpointed headers for "+ "%v: %v", fType, err) select { case <-time.After(retryTimeout): case <-b.quit: return } } // Quit if requested. select { case <-b.quit: return default: } } } // getUncheckpointedCFHeaders gets the next batch of cfheaders from the // network, if it can, and resolves any conflicts between them. It then writes // any verified headers to the store. func (b *blockManager) getUncheckpointedCFHeaders( store *headerfs.FilterHeaderStore, fType wire.FilterType) error { // Get the filter header store's chain tip. filterTip, filtHeight, err := store.ChainTip() if err != nil { return fmt.Errorf("error getting filter chain tip: %v", err) } blockHeader, blockHeight, err := b.server.BlockHeaders.ChainTip() if err != nil { return fmt.Errorf("error getting block chain tip: %v", err) } // If the block height is somehow before the filter height, then this // means that we may still be handling a re-org, so we'll bail our so // we can retry after a timeout. if blockHeight < filtHeight { return fmt.Errorf("reorg in progress, waiting to get "+ "uncheckpointed cfheaders (block height %d, filter "+ "height %d", blockHeight, filtHeight) } // If the heights match, then we're fully synced, so we don't need to // do anything from there. if blockHeight == filtHeight { log.Tracef("cfheaders already caught up to blocks") return nil } log.Infof("Attempting to fetch set of un-checkpointed filters "+ "at height=%v, hash=%v", blockHeight, blockHeader.BlockHash()) // Query all peers for the responses. startHeight := filtHeight + 1 headers, numHeaders := b.getCFHeadersForAllPeers(startHeight, fType) // Ban any peer that responds with the wrong prev filter header. for peer, msg := range headers { if msg.PrevFilterHeader != *filterTip { err := b.server.BanPeer(peer, banman.InvalidFilterHeader) if err != nil { log.Errorf("Unable to ban peer %v: %v", peer, err) } delete(headers, peer) } } if len(headers) == 0 { return fmt.Errorf("couldn't get cfheaders from peers") } // For each header, go through and check whether all headers messages // have the same filter hash. If we find a difference, get the block, // calculate the filter, and throw out any mismatching peers. for i := 0; i < numHeaders; i++ { if checkForCFHeaderMismatch(headers, i) { targetHeight := startHeight + uint32(i) badPeers, err := b.detectBadPeers( headers, targetHeight, uint32(i), fType, ) if err != nil { return err } log.Warnf("Banning %v peers due to invalid filter "+ "headers", len(badPeers)) for _, peer := range badPeers { err := b.server.BanPeer( peer, banman.InvalidFilterHeader, ) if err != nil { log.Errorf("Unable to ban peer %v: %v", peer, err) } delete(headers, peer) } } } // Get the longest filter hash chain and write it to the store. key, maxLen := "", 0 for peer, msg := range headers { if len(msg.FilterHashes) > maxLen { key, maxLen = peer, len(msg.FilterHashes) } } // We'll now fetch the set of pristine headers from the map. If ALL the // peers were banned, then we won't have a set of headers at all. We'll // return nil so we can go to the top of the loop and fetch from a new // set of peers. pristineHeaders, ok := headers[key] if !ok { return fmt.Errorf("All peers served bogus headers! Retrying " + "with new set") } _, err = b.writeCFHeadersMsg(pristineHeaders, store) return err } // getCheckpointedCFHeaders catches a filter header store up with the // checkpoints we got from the network. It assumes that the filter header store // matches the checkpoints up to the tip of the store. func (b *blockManager) getCheckpointedCFHeaders(checkpoints []*chainhash.Hash, store *headerfs.FilterHeaderStore, fType wire.FilterType) { // We keep going until we've caught up the filter header store with the // latest known checkpoint. curHeader, curHeight, err := store.ChainTip() if err != nil { panic(fmt.Sprintf("failed getting chaintip from filter "+ "store: %v", err)) } initialFilterHeader := curHeader log.Infof("Fetching set of checkpointed cfheaders filters from "+ "height=%v, hash=%v", curHeight, curHeader) // The starting interval is the checkpoint index that we'll be starting // from based on our current height in the filter header index. startingInterval := curHeight / wire.CFCheckptInterval log.Infof("Starting to query for cfheaders from "+ "checkpoint_interval=%v", startingInterval) // We'll determine how many queries we'll make based on our starting // interval and our set of checkpoints. Each query will attempt to fetch // maxCFCheckptsPerQuery intervals worth of filter headers. If // maxCFCheckptsPerQuery is not a factor of the number of checkpoint // intervals to fetch, then an additional query will exist that spans // the remaining checkpoint intervals. numCheckpts := uint32(len(checkpoints)) - startingInterval numQueries := (numCheckpts + maxCFCheckptsPerQuery - 1) / maxCFCheckptsPerQuery queryMsgs := make([]wire.Message, 0, numQueries) // We'll also create an additional set of maps that we'll use to // re-order the responses as we get them in. queryResponses := make(map[uint32]*wire.MsgCFHeaders, numQueries) stopHashes := make(map[chainhash.Hash]uint32, numQueries) // Generate all of the requests we'll be batching and space to store // the responses. Also make a map of stophash to index to make it // easier to match against incoming responses. // // TODO(roasbeef): extract to func to test currentInterval := startingInterval for currentInterval < uint32(len(checkpoints)) { // Each checkpoint is spaced wire.CFCheckptInterval after the // prior one, so we'll fetch headers in batches using the // checkpoints as a guide. Our queries will consist of // maxCFCheckptsPerQuery unless we don't have enough checkpoints // to do so. In that case, our query will consist of whatever is // left. startHeightRange := uint32( currentInterval*wire.CFCheckptInterval, ) + 1 nextInterval := currentInterval + maxCFCheckptsPerQuery if nextInterval > uint32(len(checkpoints)) { nextInterval = uint32(len(checkpoints)) } endHeightRange := uint32(nextInterval * wire.CFCheckptInterval) log.Tracef("Checkpointed cfheaders request start_range=%v, "+ "end_range=%v", startHeightRange, endHeightRange) // In order to fetch the range, we'll need the block header for // the end of the height range. stopHeader, err := b.server.BlockHeaders.FetchHeaderByHeight( endHeightRange, ) if err != nil { panic(fmt.Sprintf("failed getting block header at "+ "height %v: %v", endHeightRange, err)) } stopHash := stopHeader.BlockHash() // Once we have the stop hash, we can construct the query // message itself. queryMsg := wire.NewMsgGetCFHeaders( fType, uint32(startHeightRange), &stopHash, ) // We'll mark that the ith interval is queried by this message, // and also map the stop hash back to the index of this message. queryMsgs = append(queryMsgs, queryMsg) stopHashes[stopHash] = currentInterval // With the query starting at the current interval constructed, // we'll move onto the next one. currentInterval = nextInterval } batchesCount := len(queryMsgs) if batchesCount == 0 { return } log.Infof("Attempting to query for %v cfheader batches", batchesCount) // With the set of messages constructed, we'll now request the batch // all at once. This message will distributed the header requests // amongst all active peers, effectively sharding each query // dynamically. b.server.queryBatch( queryMsgs, // Callback to process potential replies. Always called from // the same goroutine as the outer function, so we don't have // to worry about synchronization. func(sp *ServerPeer, query wire.Message, resp wire.Message) bool { r, ok := resp.(*wire.MsgCFHeaders) if !ok { // We are only looking for cfheaders messages. return false } q, ok := query.(*wire.MsgGetCFHeaders) if !ok { // We sent a getcfheaders message, so that's // what we should be comparing against. return false } // The response doesn't match the query. if q.FilterType != r.FilterType || q.StopHash != r.StopHash { return false } checkPointIndex, ok := stopHashes[r.StopHash] if !ok { // We never requested a matching stop hash. return false } // Use either the genesis header or the previous // checkpoint index as the previous checkpoint when // verifying that the filter headers in the response // match up. prevCheckpoint := &b.genesisHeader if checkPointIndex > 0 { prevCheckpoint = checkpoints[checkPointIndex-1] } // The index of the next checkpoint will depend on // whether the query was able to allocate // maxCFCheckptsPerQuery. nextCheckPointIndex := checkPointIndex + maxCFCheckptsPerQuery - 1 if nextCheckPointIndex >= uint32(len(checkpoints)) { nextCheckPointIndex = uint32(len(checkpoints)) - 1 } nextCheckpoint := checkpoints[nextCheckPointIndex] // The response doesn't match the checkpoint. if !verifyCheckpoint(prevCheckpoint, nextCheckpoint, r) { log.Warnf("Checkpoints at index %v don't match "+ "response!!!", checkPointIndex) // If the peer gives us a header that doesn't // match what we know to be the best // checkpoint, then we'll ban the peer so we // can re-allocate the query elsewhere. peerAddr := sp.Addr() err := b.server.BanPeer( peerAddr, banman.InvalidFilterHeaderCheckpoint, ) if err != nil { log.Errorf("Unable to ban peer %v: %v", peerAddr, err) } return false } // At this point, the response matches the query, and // the relevant checkpoint we got earlier, so we should // always return true so that the peer looking for the // answer to this query can move on to the next query. // We still have to check that these headers are next // before we write them; otherwise, we cache them if // they're too far ahead, or discard them if we don't // need them. // Find the first and last height for the blocks // represented by this message. startHeight := checkPointIndex*wire.CFCheckptInterval + 1 lastHeight := (nextCheckPointIndex + 1) * wire.CFCheckptInterval log.Debugf("Got cfheaders from height=%v to "+ "height=%v, prev_hash=%v", startHeight, lastHeight, r.PrevFilterHeader) // If this is out of order but not yet written, we can // verify that the checkpoints match, and then store // them. if startHeight > curHeight+1 { log.Debugf("Got response for headers at "+ "height=%v, only at height=%v, stashing", startHeight, curHeight) queryResponses[checkPointIndex] = r return true } // If this is out of order stuff that's already been // written, we can ignore it. if lastHeight <= curHeight { log.Debugf("Received out of order reply "+ "end_height=%v, already written", lastHeight) return true } // If this is the very first range we've requested, we // may already have a portion of the headers written to // disk. // // TODO(roasbeef): can eventually special case handle // this at the top if bytes.Equal(curHeader[:], initialFilterHeader[:]) { // So we'll set the prev header to our best // known header, and seek within the header // range a bit so we don't write any duplicate // headers. r.PrevFilterHeader = *curHeader offset := curHeight + 1 - startHeight r.FilterHashes = r.FilterHashes[offset:] log.Debugf("Using offset %d for initial "+ "filter header range (new prev_hash=%v)", offset, r.PrevFilterHeader) } curHeader, err = b.writeCFHeadersMsg(r, store) if err != nil { panic(fmt.Sprintf("couldn't write cfheaders "+ "msg: %v", err)) } // Then, we cycle through any cached messages, adding // them to the batch and deleting them from the cache. for { // Determine the next checkpoint index we should // process. checkPointIndex += maxCFCheckptsPerQuery if checkPointIndex == uint32(len(checkpoints)) { checkPointIndex = uint32(len(checkpoints)) - 1 } // We'll also update the current height of the // last written set of cfheaders. curHeight = checkPointIndex * wire.CFCheckptInterval // If we don't yet have the next response, then // we'll break out so we can wait for the peers // to respond with this message. r, ok := queryResponses[checkPointIndex] if !ok { break } // We have another response to write, so delete // it from the cache and write it. delete(queryResponses, checkPointIndex) log.Debugf("Writing cfheaders at height=%v to "+ "next checkpoint", curHeight) // As we write the set of headers to disk, we // also obtain the hash of the last filter // header we've written to disk so we can // properly set the PrevFilterHeader field of // the next message. curHeader, err = b.writeCFHeadersMsg(r, store) if err != nil { panic(fmt.Sprintf("couldn't write "+ "cfheaders msg: %v", err)) } } return true }, // Same quit channel we're watching. b.quit, ) } // writeCFHeadersMsg writes a cfheaders message to the specified store. It // assumes that everything is being written in order. The hints are required to // store the correct block heights for the filters. We also return final // constructed cfheader in this range as this lets callers populate the prev // filter header field in the next message range before writing to disk. func (b *blockManager) writeCFHeadersMsg(msg *wire.MsgCFHeaders, store *headerfs.FilterHeaderStore) (*chainhash.Hash, error) { // Check that the PrevFilterHeader is the same as the last stored so we // can prevent misalignment. tip, tipHeight, err := store.ChainTip() if err != nil { return nil, err } if *tip != msg.PrevFilterHeader { return nil, fmt.Errorf("attempt to write cfheaders out of "+ "order! Tip=%v (height=%v), prev_hash=%v.", *tip, tipHeight, msg.PrevFilterHeader) } // Cycle through the headers and compute each header based on the prev // header and the filter hash from the cfheaders response entries. lastHeader := msg.PrevFilterHeader headerBatch := make([]headerfs.FilterHeader, 0, len(msg.FilterHashes)) for _, hash := range msg.FilterHashes { // header = dsha256(filterHash || prevHeader) lastHeader = chainhash.DoubleHashH( append(hash[:], lastHeader[:]...), ) headerBatch = append(headerBatch, headerfs.FilterHeader{ FilterHash: lastHeader, }) } numHeaders := len(headerBatch) // We'll now query for the set of block headers which match each of // these filters headers in their corresponding chains. Our query will // return the headers for the entire checkpoint interval ending at the // designated stop hash. blockHeaders := b.server.BlockHeaders matchingBlockHeaders, startHeight, err := blockHeaders.FetchHeaderAncestors( uint32(numHeaders-1), &msg.StopHash, ) if err != nil { return nil, err } // The final height in our range will be offset to the end of this // particular checkpoint interval. lastHeight := startHeight + uint32(numHeaders) - 1 lastBlockHeader := matchingBlockHeaders[numHeaders-1] lastHash := lastBlockHeader.BlockHash() // We only need to set the height and hash of the very last filter // header in the range to ensure that the index properly updates the // tip of the chain. headerBatch[numHeaders-1].HeaderHash = lastHash headerBatch[numHeaders-1].Height = lastHeight log.Debugf("Writing filter headers up to height=%v, hash=%v, "+ "new_tip=%v", lastHeight, lastHash, lastHeader) // Write the header batch. err = store.WriteHeaders(headerBatch...) if err != nil { return nil, err } // Notify subscribers, and also update the filter header progress // logger at the same time. for i, header := range matchingBlockHeaders { header := header headerHeight := startHeight + uint32(i) b.fltrHeaderProgessLogger.LogBlockHeight( header.Timestamp, int32(headerHeight), ) b.onBlockConnected(header, headerHeight) } // We'll also set the new header tip and notify any peers that the tip // has changed as well. Unlike the set of notifications above, this is // for sub-system that only need to know the height has changed rather // than know each new header that's been added to the tip. b.newFilterHeadersMtx.Lock() b.filterHeaderTip = lastHeight b.filterHeaderTipHash = lastHash b.newFilterHeadersMtx.Unlock() b.newFilterHeadersSignal.Broadcast() return &lastHeader, nil } // minCheckpointHeight returns the height of the last filter checkpoint for the // shortest checkpoint list among the given lists. func minCheckpointHeight(checkpoints map[string][]*chainhash.Hash) uint32 { // If the map is empty, return 0 immediately. if len(checkpoints) == 0 { return 0 } // Otherwise return the length of the shortest one. minHeight := uint32(math.MaxUint32) for _, cps := range checkpoints { height := uint32(len(cps) * wire.CFCheckptInterval) if height < minHeight { minHeight = height } } return minHeight } // verifyHeaderCheckpoint verifies that a CFHeaders message matches the passed // checkpoints. It assumes everything else has been checked, including filter // type and stop hash matches, and returns true if matching and false if not. func verifyCheckpoint(prevCheckpoint, nextCheckpoint *chainhash.Hash, cfheaders *wire.MsgCFHeaders) bool { if *prevCheckpoint != cfheaders.PrevFilterHeader { return false } lastHeader := cfheaders.PrevFilterHeader for _, hash := range cfheaders.FilterHashes { lastHeader = chainhash.DoubleHashH( append(hash[:], lastHeader[:]...), ) } return lastHeader == *nextCheckpoint } // resolveConflict finds the correct checkpoint information, rewinds the header // store if it's incorrect, and bans any peers giving us incorrect header // information. func (b *blockManager) resolveConflict( checkpoints map[string][]*chainhash.Hash, store *headerfs.FilterHeaderStore, fType wire.FilterType) ( []*chainhash.Hash, error) { // First check the served checkpoints against the hardcoded ones. for peer, cp := range checkpoints { for i, header := range cp { height := uint32((i + 1) * wire.CFCheckptInterval) err := chainsync.ControlCFHeader( b.server.chainParams, fType, height, header, ) if err == chainsync.ErrCheckpointMismatch { log.Warnf("Banning peer=%v since served "+ "checkpoints didn't match our "+ "checkpoint at height %d", peer, height) err := b.server.BanPeer( peer, banman.InvalidFilterHeaderCheckpoint, ) if err != nil { log.Errorf("Unable to ban peer %v: %v", peer, err) } delete(checkpoints, peer) break } if err != nil { return nil, err } } } if len(checkpoints) == 0 { return nil, fmt.Errorf("no peer is serving good cfheader " + "checkpoints") } // Check if the remaining checkpoints are sane. heightDiff, err := checkCFCheckptSanity(checkpoints, store) if err != nil { return nil, err } // If we got -1, we have full agreement between all peers and the store. if heightDiff == -1 { // Take the first peer's checkpoint list and return it. for _, checkpts := range checkpoints { return checkpts, nil } } log.Warnf("Detected mismatch at index=%v for checkpoints!!!", heightDiff) // Delete any responses that have fewer checkpoints than where we see a // mismatch. for peer, checkpts := range checkpoints { if len(checkpts) < heightDiff { delete(checkpoints, peer) } } if len(checkpoints) == 0 { return nil, fmt.Errorf("no peer is serving good cfheaders") } // Now we get all of the mismatched CFHeaders from peers, and check // which ones are valid. // TODO(halseth): check if peer serves headers that matches its checkpoints startHeight := uint32(heightDiff) * wire.CFCheckptInterval headers, numHeaders := b.getCFHeadersForAllPeers(startHeight, fType) // Make sure we're working off the same baseline. Otherwise, we want to // go back and get checkpoints again. var hash chainhash.Hash for _, msg := range headers { if hash == zeroHash { hash = msg.PrevFilterHeader } else if hash != msg.PrevFilterHeader { return nil, fmt.Errorf("mismatch between filter " + "headers expected to be the same") } } // For each header, go through and check whether all headers messages // have the same filter hash. If we find a difference, get the block, // calculate the filter, and throw out any mismatching peers. for i := 0; i < numHeaders; i++ { if checkForCFHeaderMismatch(headers, i) { // Get the block header for this height, along with the // block as well. targetHeight := startHeight + uint32(i) badPeers, err := b.detectBadPeers( headers, targetHeight, uint32(i), fType, ) if err != nil { return nil, err } log.Warnf("Banning %v peers due to invalid filter "+ "headers", len(badPeers)) for _, peer := range badPeers { err := b.server.BanPeer( peer, banman.InvalidFilterHeader, ) if err != nil { log.Errorf("Unable to ban peer %v: %v", peer, err) } delete(headers, peer) delete(checkpoints, peer) } } } // Any mismatches have now been thrown out. Delete any checkpoint // lists that don't have matching headers, as these are peers that // didn't respond, and ban them from future queries. for peer := range checkpoints { if _, ok := headers[peer]; !ok { err := b.server.BanPeer( peer, banman.InvalidFilterHeaderCheckpoint, ) if err != nil { log.Errorf("Unable to ban peer %v: %v", peer, err) } delete(checkpoints, peer) } } // Check sanity again. If we're sane, return a matching checkpoint // list. If not, return an error and download checkpoints from // remaining peers. heightDiff, err = checkCFCheckptSanity(checkpoints, store) if err != nil { return nil, err } // If we got -1, we have full agreement between all peers and the store. if heightDiff == -1 { // Take the first peer's checkpoint list and return it. for _, checkpts := range checkpoints { return checkpts, nil } } // Otherwise, return an error and allow the loop which calls this // function to call it again with the new set of peers. return nil, fmt.Errorf("got mismatched checkpoints") } // checkForCFHeaderMismatch checks all peers' responses at a specific position // and detects a mismatch. It returns true if a mismatch has occurred. func checkForCFHeaderMismatch(headers map[string]*wire.MsgCFHeaders, idx int) bool { // First, see if we have a mismatch. hash := zeroHash for _, msg := range headers { if len(msg.FilterHashes) <= idx { continue } if hash == zeroHash { hash = *msg.FilterHashes[idx] continue } if hash != *msg.FilterHashes[idx] { // We've found a mismatch! return true } } return false } // detectBadPeers fetches filters and the block at the given height to attempt // to detect which peers are serving bad filters. func (b *blockManager) detectBadPeers(headers map[string]*wire.MsgCFHeaders, targetHeight, filterIndex uint32, fType wire.FilterType) ([]string, error) { log.Warnf("Detected cfheader mismatch at height=%v!!!", targetHeight) // Get the block header for this height. header, err := b.server.BlockHeaders.FetchHeaderByHeight(targetHeight) if err != nil { return nil, err } // Fetch filters from the peers in question. // TODO(halseth): query only peers from headers map. filtersFromPeers := b.fetchFilterFromAllPeers( targetHeight, header.BlockHash(), fType, ) var badPeers []string for peer, msg := range headers { filter, ok := filtersFromPeers[peer] // If a peer did not respond, ban it immediately. if !ok { log.Warnf("Peer %v did not respond to filter "+ "request, considering bad", peer) badPeers = append(badPeers, peer) continue } // If the peer is serving filters that isn't consistent with // its filter hashes, ban it. hash, err := builder.GetFilterHash(filter) if err != nil { return nil, err } if hash != *msg.FilterHashes[filterIndex] { log.Warnf("Peer %v serving filters not consistent "+ "with filter hashes, considering bad.", peer) badPeers = append(badPeers, peer) } } if len(badPeers) != 0 { return badPeers, nil } // If all peers responded with consistent filters and hashes, get the // block and use it to detect who is serving bad filters. block, err := b.server.GetBlock(header.BlockHash()) if err != nil { return nil, err } log.Warnf("Attempting to reconcile cfheader mismatch amongst %v peers", len(headers)) return resolveFilterMismatchFromBlock( block.MsgBlock(), fType, filtersFromPeers, // We'll require a strict majority of our peers to agree on // filters. (len(filtersFromPeers)+2)/2, ) } // resolveFilterMismatchFromBlock will attempt to cross-reference each filter // in filtersFromPeers with the given block, based on what we can reconstruct // and verify from the filter in question. We'll return all the peers that // returned what we believe to be an invalid filter. The threshold argument is // the minimum number of peers we need to agree on a filter before banning the // other peers. // // We'll use a few strategies to figure out which peers we believe serve // invalid filters: // 1. If a peers' filter doesn't match on a script that must match, we know // the filter is invalid. // 2. If a peers' filter matches on a script that _should not_ match, it // is potentially invalid. In this case we ban peers that matches more // such scripts than other peers. // 3. If we cannot detect which filters are invalid from the block // contents, we ban peers serving filters different from the majority of // peers. func resolveFilterMismatchFromBlock(block *wire.MsgBlock, fType wire.FilterType, filtersFromPeers map[string]*gcs.Filter, threshold int) ([]string, error) { badPeers := make(map[string]struct{}) blockHash := block.BlockHash() filterKey := builder.DeriveKey(&blockHash) log.Infof("Attempting to pinpoint mismatch in cfheaders for block=%v", block.Header.BlockHash()) // Based on the type of filter, our verification algorithm will differ. // Only regular filters are currently defined. if fType != wire.GCSFilterRegular { return nil, fmt.Errorf("unknown filter: %v", fType) } // With the current set of items that we can fetch from the p2p // network, we're forced to only verify what we can at this point. So // we'll just ensure that each of the filters returned contains the // expected outputs in the block. We don't have the prev outs so we // cannot fully verify the filter. // // TODO(roasbeef): update after BLOCK_WITH_PREV_OUTS is a thing // Since we don't expect OP_RETURN scripts to be included in the block, // we keep a counter for how many matches for each peer. Since there // might be false positives, an honest peer might still match on // OP_RETURNS, but we can attempt to ban peers that have more matches // than other peers. opReturnMatches := make(map[string]int) // We'll now run through each peer and ensure that each output // script is included in the filter that they responded with to // our query. peerVerification: for peerAddr, filter := range filtersFromPeers { // We'll ensure that all the filters include every output // script within the block. // // TODO(roasbeef): eventually just do a comparison against // decompressed filters for _, tx := range block.Transactions { for _, txOut := range tx.TxOut { switch { // If the script itself is blank, then we'll // skip this as it doesn't contain any useful // information. case len(txOut.PkScript) == 0: continue // We'll also skip any OP_RETURN scripts as // well since we don't index these in order to // avoid a circular dependency. case txOut.PkScript[0] == txscript.OP_RETURN: // Previous versions of the filters did // include OP_RETURNs. To be able // disconnect bad peers still serving // these old filters we attempt to // check if there's an unexpected // match. Since there might be false // positives, an OP_RETURN can still // match filters not including them. // Therefore, we count the number of // such unexpected matches for each // peer, such that we can ban peers // matching more than the rest. match, err := filter.Match( filterKey, txOut.PkScript, ) if err != nil { // Mark peer bad if we cannot // match on its filter. log.Warnf("Unable to check "+ "filter match for "+ "peer %v, marking as "+ "bad: %v", peerAddr, err) badPeers[peerAddr] = struct{}{} continue peerVerification } // If it matches on the OP_RETURN // output, we increase the op return // counter. if match { opReturnMatches[peerAddr]++ } continue } match, err := filter.Match( filterKey, txOut.PkScript, ) if err != nil { // If we're unable to query this // filter, then we'll immediately ban // this peer. log.Warnf("Unable to check filter "+ "match for peer %v, marking "+ "as bad: %v", peerAddr, err) badPeers[peerAddr] = struct{}{} continue peerVerification } if match { continue } // If this filter doesn't match, then we'll // mark this peer as bad and move on to the // next peer. badPeers[peerAddr] = struct{}{} continue peerVerification } } } // TODO: We can add an after-the-fact countermeasure here against // eclipse attacks. If the checkpoints don't match the store, we can // check whether the store or the checkpoints we got from the network // are correct. // Return the bad peers if we have already found some. if len(badPeers) > 0 { invalidPeers := make([]string, 0, len(badPeers)) for peer := range badPeers { invalidPeers = append(invalidPeers, peer) } return invalidPeers, nil } // If we couldn't immediately detect bad peers, we check if some peers // were matching more OP_RETURNS than the rest. mostMatches := 0 for _, cnt := range opReturnMatches { if cnt > mostMatches { mostMatches = cnt } } // Gather up the peers with the most OP_RETURN matches. var potentialBans []string for peer, cnt := range opReturnMatches { if cnt == mostMatches { potentialBans = append(potentialBans, peer) } } // If only a few peers had matching OP_RETURNS, we assume they are bad. numRemaining := len(filtersFromPeers) - len(potentialBans) if len(potentialBans) > 0 && numRemaining >= threshold { log.Warnf("Found %d peers serving filters with unexpected "+ "OP_RETURNS. %d peers remaining", len(potentialBans), numRemaining) return potentialBans, nil } // If all peers where serving filters consistent with the block, we // cannot know for sure which one is dishonest (since we don't have the // prevouts to deterministically reconstruct the filter). In this // situation we go with the majority. count := make(map[chainhash.Hash]int) best := 0 for _, filter := range filtersFromPeers { hash, err := builder.GetFilterHash(filter) if err != nil { return nil, err } count[hash]++ if count[hash] > best { best = count[hash] } } // If the number of peers serving the most common filter didn't match // our threshold, there's not more we can do. if best < threshold { return nil, fmt.Errorf("only %d peers serving consistent "+ "filters, need %d", best, threshold) } // Mark all peers serving a filter other than the most common one as // bad. for peerAddr, filter := range filtersFromPeers { hash, err := builder.GetFilterHash(filter) if err != nil { return nil, err } if count[hash] < best { log.Warnf("Peer %v is serving filter with hash(%v) "+ "other than majority, marking as bad", peerAddr, hash) badPeers[peerAddr] = struct{}{} } } invalidPeers := make([]string, 0, len(badPeers)) for peer := range badPeers { invalidPeers = append(invalidPeers, peer) } return invalidPeers, nil } // getCFHeadersForAllPeers runs a query for cfheaders at a specific height and // returns a map of responses from all peers. The second return value is the // number for cfheaders in each response. func (b *blockManager) getCFHeadersForAllPeers(height uint32, fType wire.FilterType) (map[string]*wire.MsgCFHeaders, int) { // Create the map we're returning. headers := make(map[string]*wire.MsgCFHeaders) // Get the header we expect at either the tip of the block header store // or at the end of the maximum-size response message, whichever is // larger. stopHeader, stopHeight, err := b.server.BlockHeaders.ChainTip() if stopHeight-height >= wire.MaxCFHeadersPerMsg { stopHeader, err = b.server.BlockHeaders.FetchHeaderByHeight( height + wire.MaxCFHeadersPerMsg - 1, ) if err != nil { return nil, 0 } // We'll make sure we also update our stopHeight so we know how // many headers to expect below. stopHeight = height + wire.MaxCFHeadersPerMsg - 1 } // Calculate the hash and use it to create the query message. stopHash := stopHeader.BlockHash() msg := wire.NewMsgGetCFHeaders(fType, height, &stopHash) numHeaders := int(stopHeight - height + 1) // Send the query to all peers and record their responses in the map. b.server.queryAllPeers( msg, func(sp *ServerPeer, resp wire.Message, quit chan<- struct{}, peerQuit chan<- struct{}) { switch m := resp.(type) { case *wire.MsgCFHeaders: if m.StopHash == stopHash && m.FilterType == fType && len(m.FilterHashes) == numHeaders { headers[sp.Addr()] = m // We got an answer from this peer so // that peer's goroutine can stop. close(peerQuit) } } }, ) return headers, numHeaders } // fetchFilterFromAllPeers attempts to fetch a filter for the target filter // type and blocks from all peers connected to the block manager. This method // returns a map which allows the caller to match a peer to the filter it // responded with. func (b *blockManager) fetchFilterFromAllPeers( height uint32, blockHash chainhash.Hash, filterType wire.FilterType) map[string]*gcs.Filter { // We'll use this map to collate all responses we receive from each // peer. filterResponses := make(map[string]*gcs.Filter) // We'll now request the target filter from each peer, using a stop // hash at the target block hash to ensure we only get a single filter. fitlerReqMsg := wire.NewMsgGetCFilters(filterType, height, &blockHash) b.queries.queryAllPeers( fitlerReqMsg, func(sp *ServerPeer, resp wire.Message, quit chan<- struct{}, peerQuit chan<- struct{}) { switch response := resp.(type) { // We're only interested in "cfilter" messages. case *wire.MsgCFilter: // If the response doesn't match our request. // Ignore this message. if blockHash != response.BlockHash || filterType != response.FilterType { return } // Now that we know we have the proper filter, // we'll decode it into an object the caller // can utilize. gcsFilter, err := gcs.FromNBytes( builder.DefaultP, builder.DefaultM, response.Data, ) if err != nil { // Malformed filter data. We can ignore // this message. return } // Now that we're able to properly parse this // filter, we'll assign it to its source peer, // and wait for the next response. filterResponses[sp.Addr()] = gcsFilter default: } }, ) return filterResponses } // getCheckpts runs a query for cfcheckpts against all peers and returns a map // of responses. func (b *blockManager) getCheckpts(lastHash *chainhash.Hash, fType wire.FilterType) map[string][]*chainhash.Hash { checkpoints := make(map[string][]*chainhash.Hash) getCheckptMsg := wire.NewMsgGetCFCheckpt(fType, lastHash) b.queries.queryAllPeers( getCheckptMsg, func(sp *ServerPeer, resp wire.Message, quit chan<- struct{}, peerQuit chan<- struct{}) { switch m := resp.(type) { case *wire.MsgCFCheckpt: if m.FilterType == fType && m.StopHash == *lastHash { checkpoints[sp.Addr()] = m.FilterHeaders close(peerQuit) } } }, ) return checkpoints } // checkCFCheckptSanity checks whether all peers which have responded agree. // If so, it returns -1; otherwise, it returns the earliest index at which at // least one of the peers differs. The checkpoints are also checked against the // existing store up to the tip of the store. If all of the peers match but // the store doesn't, the height at which the mismatch occurs is returned. func checkCFCheckptSanity(cp map[string][]*chainhash.Hash, headerStore *headerfs.FilterHeaderStore) (int, error) { // Get the known best header to compare against checkpoints. _, storeTip, err := headerStore.ChainTip() if err != nil { return 0, err } // Determine the maximum length of each peer's checkpoint list. If they // differ, we don't return yet because we want to make sure they match // up to the shortest one. maxLen := 0 for _, checkpoints := range cp { if len(checkpoints) > maxLen { maxLen = len(checkpoints) } } // Compare the actual checkpoints against each other and anything // stored in the header store. for i := 0; i < maxLen; i++ { var checkpoint chainhash.Hash for _, checkpoints := range cp { if i >= len(checkpoints) { continue } if checkpoint == zeroHash { checkpoint = *checkpoints[i] } if checkpoint != *checkpoints[i] { log.Warnf("mismatch at %v, expected %v got "+ "%v", i, checkpoint, checkpoints[i]) return i, nil } } ckptHeight := uint32((i + 1) * wire.CFCheckptInterval) if ckptHeight <= storeTip { header, err := headerStore.FetchHeaderByHeight( ckptHeight, ) if err != nil { return i, err } if *header != checkpoint { log.Warnf("mismatch at height %v, expected %v got "+ "%v", ckptHeight, header, checkpoint) return i, nil } } } return -1, nil } // blockHandler is the main handler for the block manager. It must be run as a // goroutine. It processes block and inv messages in a separate goroutine from // the peer handlers so the block (MsgBlock) messages are handled by a single // thread without needing to lock memory data structures. This is important // because the block manager controls which blocks are needed and how // the fetching should proceed. func (b *blockManager) blockHandler() { defer b.wg.Done() candidatePeers := list.New() out: for { // Now check peer messages and quit channels. select { case m := <-b.peerChan: switch msg := m.(type) { case *newPeerMsg: b.handleNewPeerMsg(candidatePeers, msg.peer) case *invMsg: b.handleInvMsg(msg) case *headersMsg: b.handleHeadersMsg(msg) case *donePeerMsg: b.handleDonePeerMsg(candidatePeers, msg.peer) default: log.Warnf("Invalid message type in block "+ "handler: %T", msg) } case <-b.quit: break out } } log.Trace("Block handler done") } // SyncPeer returns the current sync peer. func (b *blockManager) SyncPeer() *ServerPeer { b.syncPeerMutex.Lock() defer b.syncPeerMutex.Unlock() return b.syncPeer } // isSyncCandidate returns whether or not the peer is a candidate to consider // syncing from. func (b *blockManager) isSyncCandidate(sp *ServerPeer) bool { // The peer is not a candidate for sync if it's not a full node. return sp.Services()&wire.SFNodeNetwork == wire.SFNodeNetwork } // findNextHeaderCheckpoint returns the next checkpoint after the passed height. // It returns nil when there is not one either because the height is already // later than the final checkpoint or there are none for the current network. func (b *blockManager) findNextHeaderCheckpoint(height int32) *chaincfg.Checkpoint { // There is no next checkpoint if there are none for this current // network. checkpoints := b.server.chainParams.Checkpoints if len(checkpoints) == 0 { return nil } // There is no next checkpoint if the height is already after the final // checkpoint. finalCheckpoint := &checkpoints[len(checkpoints)-1] if height >= finalCheckpoint.Height { return nil } // Find the next checkpoint. nextCheckpoint := finalCheckpoint for i := len(checkpoints) - 2; i >= 0; i-- { if height >= checkpoints[i].Height { break } nextCheckpoint = &checkpoints[i] } return nextCheckpoint } // findPreviousHeaderCheckpoint returns the last checkpoint before the passed // height. It returns a checkpoint matching the genesis block when the height // is earlier than the first checkpoint or there are no checkpoints for the // current network. This is used for resetting state when a malicious peer // sends us headers that don't lead up to a known checkpoint. func (b *blockManager) findPreviousHeaderCheckpoint(height int32) *chaincfg.Checkpoint { // Start with the genesis block - earliest checkpoint to which our code // will want to reset prevCheckpoint := &chaincfg.Checkpoint{ Height: 0, Hash: b.server.chainParams.GenesisHash, } // Find the latest checkpoint lower than height or return genesis block // if there are none. checkpoints := b.server.chainParams.Checkpoints for i := 0; i < len(checkpoints); i++ { if height <= checkpoints[i].Height { break } prevCheckpoint = &checkpoints[i] } return prevCheckpoint } // startSync will choose the best peer among the available candidate peers to // download/sync the blockchain from. When syncing is already running, it // simply returns. It also examines the candidates for any which are no longer // candidates and removes them as needed. func (b *blockManager) startSync(peers *list.List) { // Return now if we're already syncing. if b.syncPeer != nil { return } _, bestHeight, err := b.server.BlockHeaders.ChainTip() if err != nil { log.Errorf("Failed to get hash and height for the "+ "latest block: %s", err) return } var bestPeer *ServerPeer var enext *list.Element for e := peers.Front(); e != nil; e = enext { enext = e.Next() sp := e.Value.(*ServerPeer) // Remove sync candidate peers that are no longer candidates // due to passing their latest known block. // // NOTE: The < is intentional as opposed to <=. While // techcnically the peer doesn't have a later block when it's // equal, it will likely have one soon so it is a reasonable // choice. It also allows the case where both are at 0 such as // during regression test. if sp.LastBlock() < int32(bestHeight) { peers.Remove(e) continue } // TODO: Use a better algorithm to choose the best peer. // For now, just pick the candidate with the highest last block. if bestPeer == nil || sp.LastBlock() > bestPeer.LastBlock() { bestPeer = sp } } // Start syncing from the best peer if one was selected. if bestPeer != nil { locator, err := b.server.BlockHeaders.LatestBlockLocator() if err != nil { log.Errorf("Failed to get block locator for the "+ "latest block: %s", err) return } log.Infof("Syncing to block height %d from peer %s", bestPeer.LastBlock(), bestPeer.Addr()) // Now that we know we have a new sync peer, we'll lock it in // within the proper attribute. b.syncPeerMutex.Lock() b.syncPeer = bestPeer b.syncPeerMutex.Unlock() // By default will use the zero hash as our stop hash to query // for all the headers beyond our view of the network based on // our latest block locator. stopHash := &zeroHash // If we're still within the range of the set checkpoints, then // we'll use the next checkpoint to guide the set of headers we // fetch, setting our stop hash to the next checkpoint hash. if b.nextCheckpoint != nil && int32(bestHeight) < b.nextCheckpoint.Height { log.Infof("Downloading headers for blocks %d to "+ "%d from peer %s", bestHeight+1, b.nextCheckpoint.Height, bestPeer.Addr()) stopHash = b.nextCheckpoint.Hash } else { log.Infof("Fetching set of headers from tip "+ "(height=%v) from peer %s", bestHeight, bestPeer.Addr()) } // With our stop hash selected, we'll kick off the sync from // this peer with an initial GetHeaders message. b.SyncPeer().PushGetHeadersMsg(locator, stopHash) } else { log.Warnf("No sync peer candidates available") } } // IsFullySynced returns whether or not the block manager believed it is fully // synced to the connected peers, meaning both block headers and filter headers // are current. func (b *blockManager) IsFullySynced() bool { _, blockHeaderHeight, err := b.server.BlockHeaders.ChainTip() if err != nil { return false } _, filterHeaderHeight, err := b.server.RegFilterHeaders.ChainTip() if err != nil { return false } // If the block headers and filter headers are not at the same height, // we cannot be fully synced. if blockHeaderHeight != filterHeaderHeight { return false } // Block and filter headers being at the same height, return whether // our block headers are synced. return b.BlockHeadersSynced() } // BlockHeadersSynced returns whether or not the block manager believes its // block headers are synced with the connected peers. func (b *blockManager) BlockHeadersSynced() bool { b.syncPeerMutex.RLock() defer b.syncPeerMutex.RUnlock() // Figure out the latest block we know. header, height, err := b.server.BlockHeaders.ChainTip() if err != nil { return false } // There is no last checkpoint if checkpoints are disabled or there are // none for this current network. checkpoints := b.server.chainParams.Checkpoints if len(checkpoints) != 0 { // We aren't current if the newest block we know of isn't ahead // of all checkpoints. if checkpoints[len(checkpoints)-1].Height >= int32(height) { return false } } // If we have a syncPeer and are below the block we are syncing to, we // are not current. if b.syncPeer != nil && int32(height) < b.syncPeer.LastBlock() { return false } // If our time source (median times of all the connected peers) is at // least 24 hours ahead of our best known block, we aren't current. minus24Hours := b.server.timeSource.AdjustedTime().Add(-24 * time.Hour) if header.Timestamp.Before(minus24Hours) { return false } // If we have no sync peer, we can assume we're current for now. if b.syncPeer == nil { return true } // If we have a syncPeer and the peer reported a higher known block // height on connect than we know the peer already has, we're probably // not current. If the peer is lying to us, other code will disconnect // it and then we'll re-check and notice that we're actually current. return b.syncPeer.LastBlock() >= b.syncPeer.StartingHeight() } // QueueInv adds the passed inv message and peer to the block handling queue. func (b *blockManager) QueueInv(inv *wire.MsgInv, sp *ServerPeer) { // No channel handling here because peers do not need to block on inv // messages. if atomic.LoadInt32(&b.shutdown) != 0 { return } select { case b.peerChan <- &invMsg{inv: inv, peer: sp}: case <-b.quit: return } } // handleInvMsg handles inv messages from all peers. // We examine the inventory advertised by the remote peer and act accordingly. func (b *blockManager) handleInvMsg(imsg *invMsg) { // Attempt to find the final block in the inventory list. There may // not be one. lastBlock := -1 invVects := imsg.inv.InvList for i := len(invVects) - 1; i >= 0; i-- { if invVects[i].Type == wire.InvTypeBlock { lastBlock = i break } } // If this inv contains a block announcement, and this isn't coming from // our current sync peer or we're current, then update the last // announced block for this peer. We'll use this information later to // update the heights of peers based on blocks we've accepted that they // previously announced. if lastBlock != -1 && (imsg.peer != b.SyncPeer() || b.BlockHeadersSynced()) { imsg.peer.UpdateLastAnnouncedBlock(&invVects[lastBlock].Hash) } // Ignore invs from peers that aren't the sync if we are not current. // Helps prevent dealing with orphans. if imsg.peer != b.SyncPeer() && !b.BlockHeadersSynced() { return } // If our chain is current and a peer announces a block we already // know of, then update their current block height. if lastBlock != -1 && b.BlockHeadersSynced() { height, err := b.server.BlockHeaders.HeightFromHash(&invVects[lastBlock].Hash) if err == nil { imsg.peer.UpdateLastBlockHeight(int32(height)) } } // Add blocks to the cache of known inventory for the peer. for _, iv := range invVects { if iv.Type == wire.InvTypeBlock { imsg.peer.AddKnownInventory(iv) } } // If this is the sync peer or we're current, get the headers for the // announced blocks and update the last announced block. if lastBlock != -1 && (imsg.peer == b.SyncPeer() || b.BlockHeadersSynced()) { lastEl := b.headerList.Back() var lastHash chainhash.Hash if lastEl != nil { lastHash = lastEl.Header.BlockHash() } // Only send getheaders if we don't already know about the last // block hash being announced. if lastHash != invVects[lastBlock].Hash && lastEl != nil && b.lastRequested != invVects[lastBlock].Hash { // Make a locator starting from the latest known header // we've processed. locator := make(blockchain.BlockLocator, 0, wire.MaxBlockLocatorsPerMsg) locator = append(locator, &lastHash) // Add locator from the database as backup. knownLocator, err := b.server.BlockHeaders.LatestBlockLocator() if err == nil { locator = append(locator, knownLocator...) } // Get headers based on locator. err = imsg.peer.PushGetHeadersMsg(locator, &invVects[lastBlock].Hash) if err != nil { log.Warnf("Failed to send getheaders message "+ "to peer %s: %s", imsg.peer.Addr(), err) return } b.lastRequested = invVects[lastBlock].Hash } } } // QueueHeaders adds the passed headers message and peer to the block handling // queue. func (b *blockManager) QueueHeaders(headers *wire.MsgHeaders, sp *ServerPeer) { // No channel handling here because peers do not need to block on // headers messages. if atomic.LoadInt32(&b.shutdown) != 0 { return } select { case b.peerChan <- &headersMsg{headers: headers, peer: sp}: case <-b.quit: return } } // handleHeadersMsg handles headers messages from all peers. func (b *blockManager) handleHeadersMsg(hmsg *headersMsg) { msg := hmsg.headers numHeaders := len(msg.Headers) // Nothing to do for an empty headers message. if numHeaders == 0 { return } // For checking to make sure blocks aren't too far in the future as of // the time we receive the headers message. maxTimestamp := b.server.timeSource.AdjustedTime(). Add(maxTimeOffset) // We'll attempt to write the entire batch of validated headers // atomically in order to improve peformance. headerWriteBatch := make([]headerfs.BlockHeader, 0, len(msg.Headers)) // Process all of the received headers ensuring each one connects to // the previous and that checkpoints match. receivedCheckpoint := false var ( finalHash *chainhash.Hash finalHeight int32 ) for i, blockHeader := range msg.Headers { blockHash := blockHeader.BlockHash() finalHash = &blockHash // Ensure there is a previous header to compare against. prevNodeEl := b.headerList.Back() if prevNodeEl == nil { log.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, // that the proof of work is good, and that the header's // timestamp isn't too far in the future, and add it to the // list of headers. node := headerlist.Node{Header: *blockHeader} prevNode := prevNodeEl prevHash := prevNode.Header.BlockHash() if prevHash.IsEqual(&blockHeader.PrevBlock) { err := b.checkHeaderSanity(blockHeader, maxTimestamp, false) if err != nil { log.Warnf("Header doesn't pass sanity check: "+ "%s -- disconnecting peer", err) hmsg.peer.Disconnect() return } node.Height = prevNode.Height + 1 finalHeight = node.Height // This header checks out, so we'll add it to our write // batch. headerWriteBatch = append(headerWriteBatch, headerfs.BlockHeader{ BlockHeader: blockHeader, Height: uint32(node.Height), }) hmsg.peer.UpdateLastBlockHeight(node.Height) b.blkHeaderProgressLogger.LogBlockHeight( blockHeader.Timestamp, node.Height, ) // Finally initialize the header -> // map[filterHash]*peer map for filter header // validation purposes later. e := b.headerList.PushBack(node) if b.startHeader == nil { b.startHeader = e } } else { // The block doesn't connect to the last block we know. // We will need to do some additional checks to process // possible reorganizations or incorrect chain on // either our or the peer's side. // // If we got these headers from a peer that's not our // sync peer, they might not be aligned correctly or // even on the right chain. Just ignore the rest of the // message. However, if we're current, this might be a // reorg, in which case we'll either change our sync // peer or disconnect the peer that sent us these bad // headers. if hmsg.peer != b.SyncPeer() && !b.BlockHeadersSynced() { return } // Check if this is the last block we know of. This is // a shortcut for sendheaders so that each redundant // header doesn't cause a disk read. if blockHash == prevHash { continue } // Check if this block is known. If so, we continue to // the next one. _, _, err := b.server.BlockHeaders.FetchHeader(&blockHash) if err == nil { continue } // Check if the previous block is known. If it is, this // is probably a reorg based on the estimated latest // block that matches between us and the peer as // derived from the block locator we sent to request // these headers. Otherwise, the headers don't connect // to anything we know and we should disconnect the // peer. backHead, backHeight, err := b.server.BlockHeaders.FetchHeader( &blockHeader.PrevBlock, ) if err != nil { log.Warnf("Received block header that does not"+ " properly connect to the chain from"+ " peer %s (%s) -- disconnecting", hmsg.peer.Addr(), err) hmsg.peer.Disconnect() return } // We've found a branch we weren't aware of. If the // branch is earlier than the latest synchronized // checkpoint, it's invalid and we need to disconnect // the reporting peer. prevCheckpoint := b.findPreviousHeaderCheckpoint( prevNode.Height, ) if backHeight < uint32(prevCheckpoint.Height) { log.Errorf("Attempt at a reorg earlier than a "+ "checkpoint past which we've already "+ "synchronized -- disconnecting peer "+ "%s", hmsg.peer.Addr()) hmsg.peer.Disconnect() return } // Check the sanity of the new branch. If any of the // blocks don't pass sanity checks, disconnect the // peer. We also keep track of the work represented by // these headers so we can compare it to the work in // the known good chain. b.reorgList.ResetHeaderState(headerlist.Node{ Header: *backHead, Height: int32(backHeight), }) totalWork := big.NewInt(0) for j, reorgHeader := range msg.Headers[i:] { err = b.checkHeaderSanity(reorgHeader, maxTimestamp, true) if err != nil { log.Warnf("Header doesn't pass sanity"+ " check: %s -- disconnecting "+ "peer", err) hmsg.peer.Disconnect() return } totalWork.Add(totalWork, blockchain.CalcWork(reorgHeader.Bits)) b.reorgList.PushBack(headerlist.Node{ Header: *reorgHeader, Height: int32(backHeight+1) + int32(j), }) } log.Tracef("Sane reorg attempted. Total work from "+ "reorg chain: %v", totalWork) // All the headers pass sanity checks. Now we calculate // the total work for the known chain. knownWork := big.NewInt(0) // This should NEVER be nil because the most recent // block is always pushed back by resetHeaderState knownEl := b.headerList.Back() var knownHead *wire.BlockHeader for j := uint32(prevNode.Height); j > backHeight; j-- { if knownEl != nil { knownHead = &knownEl.Header knownEl = knownEl.Prev() } else { knownHead, _, err = b.server.BlockHeaders.FetchHeader( &knownHead.PrevBlock) if err != nil { log.Criticalf("Can't get block"+ "header for hash %s: "+ "%v", knownHead.PrevBlock, err) // Should we panic here? } } knownWork.Add(knownWork, blockchain.CalcWork(knownHead.Bits)) } log.Tracef("Total work from known chain: %v", knownWork) // Compare the two work totals and reject the new chain // if it doesn't have more work than the previously // known chain. Disconnect if it's actually less than // the known chain. switch knownWork.Cmp(totalWork) { case 1: log.Warnf("Reorg attempt that has less work "+ "than known chain from peer %s -- "+ "disconnecting", hmsg.peer.Addr()) hmsg.peer.Disconnect() fallthrough case 0: return default: } // At this point, we have a valid reorg, so we roll // back the existing chain and add the new block // header. We also change the sync peer. Then we can // continue with the rest of the headers in the message // as if nothing has happened. b.syncPeerMutex.Lock() b.syncPeer = hmsg.peer b.syncPeerMutex.Unlock() _, err = b.server.rollBackToHeight(backHeight) if err != nil { panic(fmt.Sprintf("Rollback failed: %s", err)) // Should we panic here? } hdrs := headerfs.BlockHeader{ BlockHeader: blockHeader, Height: backHeight + 1, } err = b.server.BlockHeaders.WriteHeaders(hdrs) if err != nil { log.Criticalf("Couldn't write block to "+ "database: %s", err) // Should we panic here? } b.headerList.ResetHeaderState(headerlist.Node{ Header: *backHead, Height: int32(backHeight), }) b.headerList.PushBack(headerlist.Node{ Header: *blockHeader, Height: int32(backHeight + 1), }) } // Verify the header at the next checkpoint height matches. if b.nextCheckpoint != nil && node.Height == b.nextCheckpoint.Height { nodeHash := node.Header.BlockHash() if nodeHash.IsEqual(b.nextCheckpoint.Hash) { receivedCheckpoint = true log.Infof("Verified downloaded block "+ "header against checkpoint at height "+ "%d/hash %s", node.Height, nodeHash) } else { log.Warnf("Block header at height %d/hash "+ "%s from peer %s does NOT match "+ "expected checkpoint hash of %s -- "+ "disconnecting", node.Height, nodeHash, hmsg.peer.Addr(), b.nextCheckpoint.Hash) prevCheckpoint := b.findPreviousHeaderCheckpoint( node.Height, ) log.Infof("Rolling back to previous validated "+ "checkpoint at height %d/hash %s", prevCheckpoint.Height, prevCheckpoint.Hash) _, err := b.server.rollBackToHeight(uint32( prevCheckpoint.Height), ) if err != nil { log.Criticalf("Rollback failed: %s", err) // Should we panic here? } hmsg.peer.Disconnect() return } break } } log.Tracef("Writing header batch of %v block headers", len(headerWriteBatch)) if len(headerWriteBatch) > 0 { // With all the headers in this batch validated, we'll write // them all in a single transaction such that this entire batch // is atomic. err := b.server.BlockHeaders.WriteHeaders(headerWriteBatch...) if err != nil { log.Errorf("Unable to write block headers: %v", err) return } } // When this header is a checkpoint, find the next checkpoint. if receivedCheckpoint { b.nextCheckpoint = b.findNextHeaderCheckpoint(finalHeight) } // If not current, request the next batch of headers starting from the // latest known header and ending with the next checkpoint. if b.server.chainParams.Net == chaincfg.SimNetParams.Net || !b.BlockHeadersSynced() { locator := blockchain.BlockLocator([]*chainhash.Hash{finalHash}) nextHash := zeroHash if b.nextCheckpoint != nil { nextHash = *b.nextCheckpoint.Hash } err := hmsg.peer.PushGetHeadersMsg(locator, &nextHash) if err != nil { log.Warnf("Failed to send getheaders message to "+ "peer %s: %s", hmsg.peer.Addr(), err) return } } // Since we have a new set of headers written to disk, we'll send out a // new signal to notify any waiting sub-systems that they can now maybe // proceed do to us extending the header chain. b.newHeadersMtx.Lock() b.headerTip = uint32(finalHeight) b.headerTipHash = *finalHash b.newHeadersMtx.Unlock() b.newHeadersSignal.Broadcast() } // checkHeaderSanity checks the PoW, and timestamp of a block header. func (b *blockManager) checkHeaderSanity(blockHeader *wire.BlockHeader, maxTimestamp time.Time, reorgAttempt bool) error { diff, err := b.calcNextRequiredDifficulty( blockHeader.Timestamp, reorgAttempt) if err != nil { return err } stubBlock := btcutil.NewBlock(&wire.MsgBlock{ Header: *blockHeader, }) err = blockchain.CheckProofOfWork(stubBlock, blockchain.CompactToBig(diff)) if err != nil { return err } // Ensure the block time is not too far in the future. if blockHeader.Timestamp.After(maxTimestamp) { return fmt.Errorf("block timestamp of %v is too far in the "+ "future", blockHeader.Timestamp) } return nil } // calcNextRequiredDifficulty calculates the required difficulty for the block // after the passed previous block node based on the difficulty retarget rules. func (b *blockManager) calcNextRequiredDifficulty(newBlockTime time.Time, reorgAttempt bool) (uint32, error) { hList := b.headerList if reorgAttempt { hList = b.reorgList } lastNode := hList.Back() // Genesis block. if lastNode == nil { return b.server.chainParams.PowLimitBits, nil } // Return the previous block's difficulty requirements if this block // is not at a difficulty retarget interval. if (lastNode.Height+1)%b.blocksPerRetarget != 0 { // For networks that support it, allow special reduction of the // required difficulty once too much time has elapsed without // mining a block. if b.server.chainParams.ReduceMinDifficulty { // Return minimum difficulty when more than the desired // amount of time has elapsed without mining a block. reductionTime := int64( b.server.chainParams.MinDiffReductionTime / time.Second) allowMinTime := lastNode.Header.Timestamp.Unix() + reductionTime if newBlockTime.Unix() > allowMinTime { return b.server.chainParams.PowLimitBits, nil } // The block was mined within the desired timeframe, so // return the difficulty for the last block which did // not have the special minimum difficulty rule applied. prevBits, err := b.findPrevTestNetDifficulty(hList) if err != nil { return 0, err } return prevBits, nil } // For the main network (or any unrecognized networks), simply // return the previous block's difficulty requirements. return lastNode.Header.Bits, nil } // Get the block node at the previous retarget (targetTimespan days // worth of blocks). firstNode, err := b.server.BlockHeaders.FetchHeaderByHeight( uint32(lastNode.Height + 1 - b.blocksPerRetarget), ) if err != nil { return 0, err } // Limit the amount of adjustment that can occur to the previous // difficulty. actualTimespan := lastNode.Header.Timestamp.Unix() - firstNode.Timestamp.Unix() adjustedTimespan := actualTimespan if actualTimespan < b.minRetargetTimespan { adjustedTimespan = b.minRetargetTimespan } else if actualTimespan > b.maxRetargetTimespan { adjustedTimespan = b.maxRetargetTimespan } // Calculate new target difficulty as: // currentDifficulty * (adjustedTimespan / targetTimespan) // The result uses integer division which means it will be slightly // rounded down. Bitcoind also uses integer division to calculate this // result. oldTarget := blockchain.CompactToBig(lastNode.Header.Bits) newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan)) targetTimeSpan := int64(b.server.chainParams.TargetTimespan / time.Second) newTarget.Div(newTarget, big.NewInt(targetTimeSpan)) // Limit new value to the proof of work limit. if newTarget.Cmp(b.server.chainParams.PowLimit) > 0 { newTarget.Set(b.server.chainParams.PowLimit) } // Log new target difficulty and return it. The new target logging is // intentionally converting the bits back to a number instead of using // newTarget since conversion to the compact representation loses // precision. newTargetBits := blockchain.BigToCompact(newTarget) log.Debugf("Difficulty retarget at block height %d", lastNode.Height+1) log.Debugf("Old target %08x (%064x)", lastNode.Header.Bits, oldTarget) log.Debugf("New target %08x (%064x)", newTargetBits, blockchain.CompactToBig(newTargetBits)) log.Debugf("Actual timespan %v, adjusted timespan %v, target timespan %v", time.Duration(actualTimespan)*time.Second, time.Duration(adjustedTimespan)*time.Second, b.server.chainParams.TargetTimespan) return newTargetBits, nil } // findPrevTestNetDifficulty returns the difficulty of the previous block which // did not have the special testnet minimum difficulty rule applied. func (b *blockManager) findPrevTestNetDifficulty(hList headerlist.Chain) (uint32, error) { startNode := hList.Back() // Genesis block. if startNode == nil { return b.server.chainParams.PowLimitBits, nil } // Search backwards through the chain for the last block without // the special rule applied. iterEl := startNode iterNode := &startNode.Header iterHeight := startNode.Height for iterNode != nil && iterHeight%b.blocksPerRetarget != 0 && iterNode.Bits == b.server.chainParams.PowLimitBits { // Get the previous block node. This function is used over // simply accessing iterNode.parent directly as it will // dynamically create previous block nodes as needed. This // helps allow only the pieces of the chain that are needed // to remain in memory. iterHeight-- el := iterEl.Prev() if el != nil { iterNode = &el.Header } else { node, err := b.server.BlockHeaders.FetchHeaderByHeight( uint32(iterHeight), ) if err != nil { log.Errorf("GetBlockByHeight: %s", err) return 0, err } iterNode = node } } // Return the found difficulty or the minimum difficulty if no // appropriate block was found. lastBits := b.server.chainParams.PowLimitBits if iterNode != nil { lastBits = iterNode.Bits } return lastBits, nil } // onBlockConnected queues a block notification that extends the current chain. func (b *blockManager) onBlockConnected(header wire.BlockHeader, height uint32) { select { case b.blockNtfnChan <- blockntfns.NewBlockConnected(header, height): case <-b.quit: } } // onBlockDisconnected queues a block notification that reorgs the current // chain. func (b *blockManager) onBlockDisconnected(headerDisconnected wire.BlockHeader, heightDisconnected uint32, newChainTip wire.BlockHeader) { select { case b.blockNtfnChan <- blockntfns.NewBlockDisconnected( headerDisconnected, heightDisconnected, newChainTip, ): case <-b.quit: } } // Notifications exposes a receive-only channel in which the latest block // notifications for the tip of the chain can be received. func (b *blockManager) Notifications() <-chan blockntfns.BlockNtfn { return b.blockNtfnChan } // NotificationsSinceHeight returns a backlog of block notifications starting // from the given height to the tip of the chain. When providing a height of 0, // a backlog will not be delivered. func (b *blockManager) NotificationsSinceHeight( height uint32) ([]blockntfns.BlockNtfn, uint32, error) { b.newFilterHeadersMtx.RLock() bestHeight := b.filterHeaderTip b.newFilterHeadersMtx.RUnlock() // If a height of 0 is provided by the caller, then a backlog of // notifications is not needed. if height == 0 { return nil, bestHeight, nil } // If the best height matches the filter header tip, then we're done and // don't need to proceed any further. if bestHeight == height { return nil, bestHeight, nil } // If the request has a height later than a height we've yet to come // across in the chain, we'll return an error to indicate so to the // caller. if height > bestHeight { return nil, 0, fmt.Errorf("request with height %d is greater "+ "than best height known %d", height, bestHeight) } // Otherwise, we need to read block headers from disk to deliver a // backlog to the caller before we proceed. blocks := make([]blockntfns.BlockNtfn, 0, bestHeight-height) for i := height + 1; i <= bestHeight; i++ { header, err := b.server.BlockHeaders.FetchHeaderByHeight(i) if err != nil { return nil, 0, err } blocks = append(blocks, blockntfns.NewBlockConnected(*header, i)) } return blocks, bestHeight, nil }