chainntnfs: rewind chain on missed disconnected blocks

If the chain backend misses telling the notifier about a series of disconnected blocks, the notifier is now able to disconnect the tip to its new best block.
This commit is contained in:
Valentine Wallace 2018-08-09 00:05:29 -07:00
parent a5e1cf9c97
commit cbf1799c40
No known key found for this signature in database
GPG Key ID: B0E55E8D1776A58D
4 changed files with 88 additions and 35 deletions

@ -327,23 +327,24 @@ out:
case chain.BlockDisconnected: case chain.BlockDisconnected:
if item.Height != b.bestBlock.Height { if item.Height != b.bestBlock.Height {
chainntnfs.Log.Warnf("Received blocks "+ chainntnfs.Log.Infof("Missed disconnected" +
"out of order: current height="+ "blocks, attempting to catch up")
"%d, disconnected height=%d",
bestHeight, item.Height)
continue
} }
chainntnfs.Log.Infof("Block disconnected from "+ newBestBlock, err := chainntnfs.RewindChain(
"main chain: height=%v, sha=%v", b.chainConn, b.txConfNotifier,
item.Height, item.Hash) b.bestBlock, item.Height-1,
)
err := b.txConfNotifier.DisconnectTip(
uint32(item.Height))
if err != nil { if err != nil {
chainntnfs.Log.Error(err) chainntnfs.Log.Errorf("Unable to rewind chain "+
"from height %d to height %d: %v",
b.bestBlock.Height, item.Height-1, err)
} }
// Set the bestBlock here in case a chain
// rewind partially completed.
b.bestBlock = newBestBlock
case chain.RelevantTx: case chain.RelevantTx:
b.handleRelevantTx(item, b.bestBlock.Height) b.handleRelevantTx(item, b.bestBlock.Height)
} }

@ -397,20 +397,24 @@ out:
} }
if update.blockHeight != b.bestBlock.Height { if update.blockHeight != b.bestBlock.Height {
chainntnfs.Log.Warnf("Received blocks out of order: "+ chainntnfs.Log.Infof("Missed disconnected" +
"current height=%d, disconnected height=%d", "blocks, attempting to catch up")
currentHeight, update.blockHeight)
continue
} }
chainntnfs.Log.Infof("Block disconnected from main chain: "+ newBestBlock, err := chainntnfs.RewindChain(
"height=%v, sha=%v", update.blockHeight, update.blockHash) b.chainConn, b.txConfNotifier, b.bestBlock,
update.blockHeight-1,
err := b.txConfNotifier.DisconnectTip(uint32(update.blockHeight)) )
if err != nil { if err != nil {
chainntnfs.Log.Error(err) chainntnfs.Log.Errorf("Unable to rewind chain "+
"from height %d to height %d: %v",
b.bestBlock.Height, update.blockHeight-1, err)
} }
// Set the bestBlock here in case a chain rewind
// partially completed.
b.bestBlock = newBestBlock
// NOTE: we currently only use txUpdates for mempool spends and // NOTE: we currently only use txUpdates for mempool spends and
// rescan spends. It might get removed entirely in the future. // rescan spends. It might get removed entirely in the future.
case item := <-b.txUpdates.ChanOut(): case item := <-b.txUpdates.ChanOut():

@ -344,6 +344,40 @@ func GetClientMissedBlocks(chainConn ChainConn, clientBestBlock *BlockEpoch,
return missedBlocks, nil return missedBlocks, nil
} }
// RewindChain handles internal state updates for the notifier's TxConfNotifier
// It has no effect if given a height greater than or equal to our current best
// known height. It returns the new best block for the notifier.
func RewindChain(chainConn ChainConn, txConfNotifier *TxConfNotifier,
currBestBlock BlockEpoch, targetHeight int32) (BlockEpoch, error) {
newBestBlock := BlockEpoch{
Height: currBestBlock.Height,
Hash: currBestBlock.Hash,
}
for height := currBestBlock.Height; height > targetHeight; height-- {
hash, err := chainConn.GetBlockHash(int64(height - 1))
if err != nil {
return newBestBlock, fmt.Errorf("unable to "+
"find blockhash for disconnected height=%d: %v",
height, err)
}
Log.Infof("Block disconnected from main chain: "+
"height=%v, sha=%v", height, newBestBlock.Hash)
err = txConfNotifier.DisconnectTip(uint32(height))
if err != nil {
return newBestBlock, fmt.Errorf("unable to "+
" disconnect tip for height=%d: %v",
height, err)
}
newBestBlock.Height = height - 1
newBestBlock.Hash = hash
}
return newBestBlock, nil
}
// getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight) // getMissedBlocks returns a slice of blocks: [startingHeight, endingHeight)
// fetched from the chain. // fetched from the chain.
func getMissedBlocks(chainConn ChainConn, startingHeight, func getMissedBlocks(chainConn ChainConn, startingHeight,

@ -365,6 +365,7 @@ func (n *NeutrinoNotifier) notificationDispatcher() {
"to update rescan: %v", "to update rescan: %v",
err) err)
} }
}() }()
case *blockEpochRegistration: case *blockEpochRegistration:
@ -417,24 +418,37 @@ func (n *NeutrinoNotifier) notificationDispatcher() {
} }
n.heightMtx.Lock() n.heightMtx.Lock()
if update.height != n.bestHeight { if update.height != uint32(n.bestHeight) {
chainntnfs.Log.Warnf("Received blocks out of order: "+ chainntnfs.Log.Infof("Missed disconnected" +
"current height=%d, disconnected height=%d", "blocks, attempting to catch up")
n.bestHeight, update.height)
n.heightMtx.Unlock()
continue
} }
n.bestHeight = update.height - 1 header, err := n.p2pNode.BlockHeaders.FetchHeaderByHeight(
n.heightMtx.Unlock() n.bestHeight,
)
chainntnfs.Log.Infof("Block disconnected from main chain: "+
"height=%v, sha=%v", update.height, update.hash)
err := n.txConfNotifier.DisconnectTip(update.height)
if err != nil { if err != nil {
chainntnfs.Log.Error(err) chainntnfs.Log.Errorf("Unable to fetch header"+
"for height %d: %v", n.bestHeight, err)
} }
hash := header.BlockHash()
notifierBestBlock := chainntnfs.BlockEpoch{
Height: int32(n.bestHeight),
Hash: &hash,
}
newBestBlock, err := chainntnfs.RewindChain(
n.chainConn, n.txConfNotifier, notifierBestBlock,
int32(update.height-1),
)
if err != nil {
chainntnfs.Log.Errorf("Unable to rewind chain "+
"from height %d to height %d: %v",
n.bestHeight, update.height-1, err)
}
// Set the bestHeight here in case a chain rewind
// partially completed.
n.bestHeight = uint32(newBestBlock.Height)
n.heightMtx.Unlock()
case err := <-n.rescanErr: case err := <-n.rescanErr:
chainntnfs.Log.Errorf("Error during rescan: %v", err) chainntnfs.Log.Errorf("Error during rescan: %v", err)