bmgr: Remove block manager chain state.

This removes the block manager chain state in favor of using the
blockchain.BlockChain instance now that it is safe for concurrency.
This commit is contained in:
Dave Collins 2016-04-02 02:12:38 -05:00
parent 2cfc6478ce
commit 2274d36333
4 changed files with 26 additions and 104 deletions

View file

@ -135,31 +135,6 @@ type headerNode struct {
hash *chainhash.Hash hash *chainhash.Hash
} }
// chainState tracks the state of the best chain as blocks are inserted. This
// is done because btcchain is currently not safe for concurrent access and the
// block manager is typically quite busy processing block and inventory.
// Therefore, requesting this information from chain through the block manager
// would not be anywhere near as efficient as simply updating it as each block
// is inserted and protecting it with a mutex.
type chainState struct {
sync.Mutex
newestHash *chainhash.Hash
newestHeight int32
pastMedianTime time.Time
pastMedianTimeErr error
}
// Best returns the block hash and height known for the tip of the best known
// chain.
//
// This function is safe for concurrent access.
func (c *chainState) Best() (*chainhash.Hash, int32) {
c.Lock()
defer c.Unlock()
return c.newestHash, c.newestHeight
}
// blockManager provides a concurrency safe block manager for handling all // blockManager provides a concurrency safe block manager for handling all
// incoming blocks. // incoming blocks.
type blockManager struct { type blockManager struct {
@ -176,7 +151,6 @@ type blockManager struct {
processingReqs bool processingReqs bool
syncPeer *serverPeer syncPeer *serverPeer
msgChan chan interface{} msgChan chan interface{}
chainState chainState
wg sync.WaitGroup wg sync.WaitGroup
quit chan struct{} quit chan struct{}
@ -203,20 +177,6 @@ func (b *blockManager) resetHeaderState(newestHash *chainhash.Hash, newestHeight
} }
} }
// updateChainState updates the chain state associated with the block manager.
// This allows fast access to chain information since btcchain is currently not
// safe for concurrent access and the block manager is typically quite busy
// processing block and inventory.
func (b *blockManager) updateChainState(newestHash *chainhash.Hash, newestHeight int32) {
b.chainState.Lock()
defer b.chainState.Unlock()
b.chainState.newestHash = newestHash
b.chainState.newestHeight = newestHeight
b.chainState.pastMedianTime = b.chain.BestSnapshot().MedianTime
b.chainState.pastMedianTimeErr = nil
}
// findNextHeaderCheckpoint returns the next checkpoint after the passed height. // findNextHeaderCheckpoint returns the next checkpoint after the passed height.
// It returns nil when there is not one either because the height is already // It returns nil when there is not one either because the height is already
// later than the final checkpoint or some other reason such as disabled // later than the final checkpoint or some other reason such as disabled
@ -635,14 +595,9 @@ func (b *blockManager) handleBlockMsg(bmsg *blockMsg) {
// update the chain state. // update the chain state.
b.progressLogger.LogBlockHeight(bmsg.block) b.progressLogger.LogBlockHeight(bmsg.block)
// Query the chain for the latest best block since the block
// that was processed could be on a side chain or have caused
// a reorg.
best := b.chain.BestSnapshot()
b.updateChainState(best.Hash, best.Height)
// Update this peer's latest block height, for future // Update this peer's latest block height, for future
// potential sync node candidacy. // potential sync node candidacy.
best := b.chain.BestSnapshot()
heightUpdate = best.Height heightUpdate = best.Height
blkHashUpdate = best.Hash blkHashUpdate = best.Hash
@ -1126,12 +1081,6 @@ out:
} }
} }
// Query the chain for the latest best block
// since the block that was processed could be
// on a side chain or have caused a reorg.
best := b.chain.BestSnapshot()
b.updateChainState(best.Hash, best.Height)
// Allow any clients performing long polling via the // Allow any clients performing long polling via the
// getblocktemplate RPC to be notified when the new block causes // getblocktemplate RPC to be notified when the new block causes
// their old block template to become stale. // their old block template to become stale.
@ -1419,10 +1368,6 @@ func newBlockManager(s *server, indexManager blockchain.IndexManager) (*blockMan
bmgrLog.Info("Checkpoints are disabled") bmgrLog.Info("Checkpoints are disabled")
} }
// Initialize the chain state now that the initial block node index has
// been generated.
bm.updateChainState(best.Hash, best.Height)
return &bm, nil return &bm, nil
} }

View file

@ -125,7 +125,7 @@ func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
// detected and all work on the stale block is halted to start work on // detected and all work on the stale block is halted to start work on
// a new block, but the check only happens periodically, so it is // a new block, but the check only happens periodically, so it is
// possible a block was found and submitted in between. // possible a block was found and submitted in between.
latestHash, _ := m.server.blockManager.chainState.Best() latestHash := m.server.blockManager.chain.BestSnapshot().Hash
msgBlock := block.MsgBlock() msgBlock := block.MsgBlock()
if !msgBlock.Header.PrevBlock.IsEqual(latestHash) { if !msgBlock.Header.PrevBlock.IsEqual(latestHash) {
minrLog.Debugf("Block submitted via CPU miner with previous "+ minrLog.Debugf("Block submitted via CPU miner with previous "+
@ -213,8 +213,8 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
// The current block is stale if the best block // The current block is stale if the best block
// has changed. // has changed.
bestHash, _ := m.server.blockManager.chainState.Best() best := m.server.blockManager.chain.BestSnapshot()
if !header.PrevBlock.IsEqual(bestHash) { if !header.PrevBlock.IsEqual(best.Hash) {
return false return false
} }
@ -292,7 +292,7 @@ out:
// this would otherwise end up building a new block template on // this would otherwise end up building a new block template on
// a block that is in the process of becoming stale. // a block that is in the process of becoming stale.
m.submitBlockLock.Lock() m.submitBlockLock.Lock()
_, curHeight := m.server.blockManager.chainState.Best() curHeight := m.server.blockManager.chain.BestSnapshot().Height
if curHeight != 0 && !m.server.blockManager.IsCurrent() { if curHeight != 0 && !m.server.blockManager.IsCurrent() {
m.submitBlockLock.Unlock() m.submitBlockLock.Unlock()
time.Sleep(time.Second) time.Sleep(time.Second)
@ -559,7 +559,7 @@ func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) {
// be changing and this would otherwise end up building a new block // be changing and this would otherwise end up building a new block
// template on a block that is in the process of becoming stale. // template on a block that is in the process of becoming stale.
m.submitBlockLock.Lock() m.submitBlockLock.Lock()
_, curHeight := m.server.blockManager.chainState.Best() curHeight := m.server.blockManager.chain.BestSnapshot().Height
// Choose a payment address at random. // Choose a payment address at random.
rand.Seed(time.Now().UnixNano()) rand.Seed(time.Now().UnixNano())

View file

@ -280,26 +280,14 @@ func logSkippedDeps(tx *btcutil.Tx, deps map[chainhash.Hash]*txPrioItem) {
// on the end of the current best chain. In particular, it is one second after // on the end of the current best chain. In particular, it is one second after
// the median timestamp of the last several blocks per the chain consensus // the median timestamp of the last several blocks per the chain consensus
// rules. // rules.
func minimumMedianTime(chainState *chainState) (time.Time, error) { func minimumMedianTime(chainState *blockchain.BestState) time.Time {
chainState.Lock() return chainState.MedianTime.Add(time.Second)
defer chainState.Unlock()
if chainState.pastMedianTimeErr != nil {
return time.Time{}, chainState.pastMedianTimeErr
}
return chainState.pastMedianTime.Add(time.Second), nil
} }
// medianAdjustedTime returns the current time adjusted to ensure it is at least // medianAdjustedTime returns the current time adjusted to ensure it is at least
// one second after the median timestamp of the last several blocks per the // one second after the median timestamp of the last several blocks per the
// chain consensus rules. // chain consensus rules.
func medianAdjustedTime(chainState *chainState, timeSource blockchain.MedianTimeSource) (time.Time, error) { func medianAdjustedTime(chainState *blockchain.BestState, timeSource blockchain.MedianTimeSource) time.Time {
chainState.Lock()
defer chainState.Unlock()
if chainState.pastMedianTimeErr != nil {
return time.Time{}, chainState.pastMedianTimeErr
}
// The timestamp for the block must not be before the median timestamp // The timestamp for the block must not be before the median timestamp
// of the last several blocks. Thus, choose the maximum between the // of the last several blocks. Thus, choose the maximum between the
// current time and one second after the past median time. The current // current time and one second after the past median time. The current
@ -307,12 +295,12 @@ func medianAdjustedTime(chainState *chainState, timeSource blockchain.MedianTime
// block timestamp does not supported a precision greater than one // block timestamp does not supported a precision greater than one
// second. // second.
newTimestamp := timeSource.AdjustedTime() newTimestamp := timeSource.AdjustedTime()
minTimestamp := chainState.pastMedianTime.Add(time.Second) minTimestamp := minimumMedianTime(chainState)
if newTimestamp.Before(minTimestamp) { if newTimestamp.Before(minTimestamp) {
newTimestamp = minTimestamp newTimestamp = minTimestamp
} }
return newTimestamp, nil return newTimestamp
} }
// NewBlockTemplate returns a new block template that is ready to be solved // NewBlockTemplate returns a new block template that is ready to be solved
@ -381,13 +369,11 @@ func NewBlockTemplate(policy *mining.Policy, server *server, payToAddress btcuti
var txSource mining.TxSource = server.txMemPool var txSource mining.TxSource = server.txMemPool
blockManager := server.blockManager blockManager := server.blockManager
timeSource := server.timeSource timeSource := server.timeSource
chainState := &blockManager.chainState
// Extend the most recently known best block. // Extend the most recently known best block.
chainState.Lock() best := blockManager.chain.BestSnapshot()
prevHash := chainState.newestHash prevHash := best.Hash
nextBlockHeight := chainState.newestHeight + 1 nextBlockHeight := best.Height + 1
chainState.Unlock()
// Create a standard coinbase transaction paying to the provided // Create a standard coinbase transaction paying to the provided
// address. NOTE: The coinbase value will be updated to include the // address. NOTE: The coinbase value will be updated to include the
@ -701,10 +687,7 @@ mempoolLoop:
// Calculate the required difficulty for the block. The timestamp // Calculate the required difficulty for the block. The timestamp
// is potentially adjusted to ensure it comes after the median time of // is potentially adjusted to ensure it comes after the median time of
// the last several blocks per the chain consensus rules. // the last several blocks per the chain consensus rules.
ts, err := medianAdjustedTime(chainState, timeSource) ts := medianAdjustedTime(best, timeSource)
if err != nil {
return nil, err
}
reqDifficulty, err := blockManager.chain.CalcNextRequiredDifficulty(ts) reqDifficulty, err := blockManager.chain.CalcNextRequiredDifficulty(ts)
if err != nil { if err != nil {
return nil, err return nil, err
@ -759,11 +742,8 @@ func UpdateBlockTime(msgBlock *wire.MsgBlock, bManager *blockManager) error {
// The new timestamp is potentially adjusted to ensure it comes after // The new timestamp is potentially adjusted to ensure it comes after
// the median time of the last several blocks per the chain consensus // the median time of the last several blocks per the chain consensus
// rules. // rules.
newTimestamp, err := medianAdjustedTime(&bManager.chainState, best := bManager.chain.BestSnapshot()
bManager.server.timeSource) newTimestamp := medianAdjustedTime(best, bManager.server.timeSource)
if err != nil {
return err
}
msgBlock.Header.Timestamp = newTimestamp msgBlock.Header.Timestamp = newTimestamp
// If running on a network that requires recalculating the difficulty, // If running on a network that requires recalculating the difficulty,

View file

@ -1363,7 +1363,7 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
// generated. // generated.
var msgBlock *wire.MsgBlock var msgBlock *wire.MsgBlock
var targetDifficulty string var targetDifficulty string
latestHash, _ := s.server.blockManager.chainState.Best() latestHash := s.server.blockManager.chain.BestSnapshot().Hash
template := state.template template := state.template
if template == nil || state.prevHash == nil || if template == nil || state.prevHash == nil ||
!state.prevHash.IsEqual(latestHash) || !state.prevHash.IsEqual(latestHash) ||
@ -1402,12 +1402,8 @@ func (state *gbtWorkState) updateBlockTemplate(s *rpcServer, useCoinbaseValue bo
// Find the minimum allowed timestamp for the block based on the // Find the minimum allowed timestamp for the block based on the
// median timestamp of the last several blocks per the chain // median timestamp of the last several blocks per the chain
// consensus rules. // consensus rules.
chainState := &s.server.blockManager.chainState best := s.server.blockManager.chain.BestSnapshot()
minTimestamp, err := minimumMedianTime(chainState) minTimestamp := minimumMedianTime(best)
if err != nil {
context := "Failed to get minimum median time"
return internalRPCError(err.Error(), context)
}
// Update work state to ensure another block template isn't // Update work state to ensure another block template isn't
// generated until needed. // generated until needed.
@ -1760,7 +1756,7 @@ func handleGetBlockTemplateRequest(s *rpcServer, request *btcjson.TemplateReques
} }
// No point in generating or accepting work before the chain is synced. // No point in generating or accepting work before the chain is synced.
_, currentHeight := s.server.blockManager.chainState.Best() currentHeight := s.server.blockManager.chain.BestSnapshot().Height
if currentHeight != 0 && !s.server.blockManager.IsCurrent() { if currentHeight != 0 && !s.server.blockManager.IsCurrent() {
return nil, &btcjson.RPCError{ return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCClientInInitialDownload, Code: btcjson.ErrRPCClientInInitialDownload,
@ -1923,7 +1919,7 @@ func handleGetBlockTemplateProposal(s *rpcServer, request *btcjson.TemplateReque
block := btcutil.NewBlock(&msgBlock) block := btcutil.NewBlock(&msgBlock)
// Ensure the block is building from the expected previous block. // Ensure the block is building from the expected previous block.
expectedPrevHash, _ := s.server.blockManager.chainState.Best() expectedPrevHash := s.server.blockManager.chain.BestSnapshot().Hash
prevHash := &block.MsgBlock().Header.PrevBlock prevHash := &block.MsgBlock().Header.PrevBlock
if expectedPrevHash == nil || !expectedPrevHash.IsEqual(prevHash) { if expectedPrevHash == nil || !expectedPrevHash.IsEqual(prevHash) {
return "bad-prevblk", nil return "bad-prevblk", nil
@ -2540,7 +2536,8 @@ func handleGetWorkRequest(s *rpcServer) (interface{}, error) {
// and it has been at least one minute since the last template was // and it has been at least one minute since the last template was
// generated. // generated.
lastTxUpdate := s.server.txMemPool.LastUpdated() lastTxUpdate := s.server.txMemPool.LastUpdated()
latestHash, latestHeight := s.server.blockManager.chainState.Best() best := s.server.blockManager.chain.BestSnapshot()
latestHash, latestHeight := best.Hash, best.Height
msgBlock := state.msgBlock msgBlock := state.msgBlock
if msgBlock == nil || state.prevHash == nil || if msgBlock == nil || state.prevHash == nil ||
!state.prevHash.IsEqual(latestHash) || !state.prevHash.IsEqual(latestHash) ||
@ -2769,7 +2766,7 @@ func handleGetWorkSubmission(s *rpcServer, hexData string) (interface{}, error)
return false, nil return false, nil
} }
latestHash, _ := s.server.blockManager.chainState.Best() latestHash := s.server.blockManager.chain.BestSnapshot().Hash
if !msgBlock.Header.PrevBlock.IsEqual(latestHash) { if !msgBlock.Header.PrevBlock.IsEqual(latestHash) {
rpcsLog.Debugf("Block submitted via getwork with previous "+ rpcsLog.Debugf("Block submitted via getwork with previous "+
"block %s is stale", msgBlock.Header.PrevBlock) "block %s is stale", msgBlock.Header.PrevBlock)
@ -2821,7 +2818,7 @@ func handleGetWork(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
} }
// No point in generating or accepting work before the chain is synced. // No point in generating or accepting work before the chain is synced.
_, currentHeight := s.server.blockManager.chainState.Best() currentHeight := s.server.blockManager.chain.BestSnapshot().Height
if currentHeight != 0 && !s.server.blockManager.IsCurrent() { if currentHeight != 0 && !s.server.blockManager.IsCurrent() {
return nil, &btcjson.RPCError{ return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCClientInInitialDownload, Code: btcjson.ErrRPCClientInInitialDownload,