Repository: kaspanet/kaspad
Pull request #2023 (2022-04-15 15:06:36)

Block template cache improvement
* Avoid concurrent calls to template builder
* Clear cache on new block event
* Cache ShouldMine call

codecov[bot]: # [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) Report
> Merging [#2023](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) (a14c18f) into [dev](https://codecov.io/gh/kaspanet/kaspad/commit/beb038c815698c1863aef4fff67690a8546166c8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) (beb038c) will **decrease** coverage by `0.00%`.
> The diff coverage is `73.07%`.

> :exclamation: Current head a14c18f differs from pull request most recent head 3409a45. Consider uploading reports for the commit 3409a45 to get more accurate results

```diff
@@            Coverage Diff             @@
##              dev    #2023      +/-   ##
==========================================
- Coverage   59.39%   59.39%   -0.01%     
==========================================
  Files         671      671              
  Lines       32028    32023       -5     
==========================================
- Hits        19023    19019       -4     
+ Misses      10276    10270       -6     
- Partials     2729     2734       +5     
```


| [Impacted Files](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) | Coverage Δ | |
|---|---|---|
| [domain/miningmanager/miningmanager.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-ZG9tYWluL21pbmluZ21hbmFnZXIvbWluaW5nbWFuYWdlci5nbw==) | `86.04% <70.58%> (+23.30%)` | :arrow_up: |
| [app/rpc/rpchandlers/get\_block\_template.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-YXBwL3JwYy9ycGNoYW5kbGVycy9nZXRfYmxvY2tfdGVtcGxhdGUuZ28=) | `41.66% <71.42%> (+2.53%)` | :arrow_up: |
| [app/protocol/flowcontext/blocks.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-YXBwL3Byb3RvY29sL2Zsb3djb250ZXh0L2Jsb2Nrcy5nbw==) | `69.79% <100.00%> (+0.31%)` | :arrow_up: |
| [app/rpc/rpccontext/context.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-YXBwL3JwYy9ycGNjb250ZXh0L2NvbnRleHQuZ28=) | `100.00% <100.00%> (ø)` | |
| [infrastructure/network/rpcclient/rpc\_get\_info.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvbmV0d29yay9ycGNjbGllbnQvcnBjX2dldF9pbmZvLmdv) | `45.45% <0.00%> (-18.19%)` | :arrow_down: |
| [infrastructure/network/rpcclient/rpcclient.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvbmV0d29yay9ycGNjbGllbnQvcnBjY2xpZW50Lmdv) | `60.67% <0.00%> (-6.75%)` | :arrow_down: |
| [infrastructure/network/netadapter/router/route.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvbmV0d29yay9uZXRhZGFwdGVyL3JvdXRlci9yb3V0ZS5nbw==) | `82.85% <0.00%> (-5.72%)` | :arrow_down: |
| [...ructure/network/rpcclient/grpcclient/grpcclient.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvbmV0d29yay9ycGNjbGllbnQvZ3JwY2NsaWVudC9ncnBjY2xpZW50Lmdv) | `76.56% <0.00%> (-4.69%)` | :arrow_down: |
| [.../protocol/flows/v5/blockrelay/handle\_relay\_invs.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-YXBwL3Byb3RvY29sL2Zsb3dzL3Y1L2Jsb2NrcmVsYXkvaGFuZGxlX3JlbGF5X2ludnMuZ28=) | `52.63% <0.00%> (-0.81%)` | :arrow_down: |
| [...k/netadapter/server/grpcserver/connection\_loops.go](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet#diff-aW5mcmFzdHJ1Y3R1cmUvbmV0d29yay9uZXRhZGFwdGVyL3NlcnZlci9ncnBjc2VydmVyL2Nvbm5lY3Rpb25fbG9vcHMuZ28=) | `58.62% <0.00%> (+1.72%)` | :arrow_up: |
| ... and [1 more](https://codecov.io/gh/kaspanet/kaspad/pull/2023/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet). Last update [beb038c...3409a45](https://codecov.io/gh/kaspanet/kaspad/pull/2023?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kaspanet).


Patch:
diff --git a/app/protocol/flowcontext/blocks.go b/app/protocol/flowcontext/blocks.go
index 3331df54..994bf259 100644
--- a/app/protocol/flowcontext/blocks.go
+++ b/app/protocol/flowcontext/blocks.go
@@ -70,6 +70,9 @@ func (f *FlowContext) OnVirtualChange(virtualChangeSet *externalapi.VirtualChang
 
 // OnNewBlockTemplate calls the handler function whenever a new block template is available for miners.
 func (f *FlowContext) OnNewBlockTemplate() error {
+	// Clear current template cache. Note we call this even if the handler is nil, in order to keep the
+	// state consistent without dependency on external event registration
+	f.Domain().MiningManager().ClearBlockTemplate()
 	if f.onNewBlockTemplateHandler != nil {
 		return f.onNewBlockTemplateHandler()
 	}
diff --git a/app/protocol/flowcontext/flow_context.go b/app/protocol/flowcontext/flow_context.go
index bd17714f..a9103b42 100644
--- a/app/protocol/flowcontext/flow_context.go
+++ b/app/protocol/flowcontext/flow_context.go
@@ -53,9 +53,8 @@ type FlowContext struct {
 	onPruningPointUTXOSetOverrideHandler OnPruningPointUTXOSetOverrideHandler
 	onTransactionAddedToMempoolHandler   OnTransactionAddedToMempoolHandler
 
-	expectedDAAWindowDurationInMilliseconds int64
-	lastRebroadcastTime                     time.Time
-	sharedRequestedTransactions             *SharedRequestedTransactions
+	lastRebroadcastTime         time.Time
+	sharedRequestedTransactions *SharedRequestedTransactions
 
 	sharedRequestedBlocks *SharedRequestedBlocks
 
@@ -93,8 +92,6 @@ func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanage
 		transactionIDsToPropagate:        []*externalapi.DomainTransactionID{},
 		lastTransactionIDPropagationTime: time.Now(),
 		shutdownChan:                     make(chan struct{}),
-		expectedDAAWindowDurationInMilliseconds: cfg.NetParams().TargetTimePerBlock.Milliseconds() *
-			int64(cfg.NetParams().DifficultyAdjustmentWindowSize),
 	}
 }
 
@@ -109,6 +106,11 @@ func (f *FlowContext) ShutdownChan() <-chan struct{} {
 	return f.shutdownChan
 }
 
+// IsNearlySynced returns whether current consensus is considered synced or close to being synced.
+func (f *FlowContext) IsNearlySynced() (bool, error) {
+	return f.Domain().Consensus().IsNearlySynced()
+}
+
 // SetOnVirtualChangeHandler sets the onVirtualChangeHandler handler
 func (f *FlowContext) SetOnVirtualChangeHandler(onVirtualChangeHandler OnVirtualChangeHandler) {
 	f.onVirtualChangeHandler = onVirtualChangeHandler
diff --git a/app/protocol/flowcontext/network.go b/app/protocol/flowcontext/network.go
index 1533baed..ca8c1f05 100644
--- a/app/protocol/flowcontext/network.go
+++ b/app/protocol/flowcontext/network.go
@@ -72,3 +72,10 @@ func (f *FlowContext) Peers() []*peerpkg.Peer {
 	}
 	return peers
 }
+
+// HasPeers returns whether there are currently active peers
+func (f *FlowContext) HasPeers() bool {
+	f.peersMutex.RLock()
+	defer f.peersMutex.RUnlock()
+	return len(f.peers) > 0
+}
diff --git a/app/protocol/flowcontext/should_mine.go b/app/protocol/flowcontext/should_mine.go
deleted file mode 100644
index f0c01d94..00000000
--- a/app/protocol/flowcontext/should_mine.go
+++ /dev/null
@@ -1,40 +0,0 @@
-package flowcontext
-
-import "github.com/kaspanet/kaspad/util/mstime"
-
-// IsNearlySynced returns whether this node is considered synced or close to being synced. This info
-// is used to determine if it's ok to use a block template from this node for mining purposes.
-func (f *FlowContext) IsNearlySynced() (bool, error) {
-	peers := f.Peers()
-	if len(peers) == 0 {
-		log.Debugf("The node is not connected to peers, so IsNearlySynced returns false")
-		return false, nil
-	}
-
-	virtualSelectedParent, err := f.domain.Consensus().GetVirtualSelectedParent()
-	if err != nil {
-		return false, err
-	}
-
-	if virtualSelectedParent.Equal(f.Config().NetParams().GenesisHash) {
-		return false, nil
-	}
-
-	virtualSelectedParentHeader, err := f.domain.Consensus().GetBlockHeader(virtualSelectedParent)
-	if err != nil {
-		return false, err
-	}
-
-	now := mstime.Now().UnixMilliseconds()
-	// As a heuristic, we allow the node to mine if he is likely to be within the current DAA window of fully synced nodes.
-	// Such blocks contribute to security by maintaining the current difficulty despite possibly being slightly out of sync.
-	if now-virtualSelectedParentHeader.TimeInMilliseconds() < f.expectedDAAWindowDurationInMilliseconds {
-		log.Debugf("The selected tip timestamp is recent (%d), so IsNearlySynced returns true",
-			virtualSelectedParentHeader.TimeInMilliseconds())
-		return true, nil
-	}
-
-	log.Debugf("The selected tip timestamp is old (%d), so IsNearlySynced returns false",
-		virtualSelectedParentHeader.TimeInMilliseconds())
-	return false, nil
-}
diff --git a/app/protocol/manager.go b/app/protocol/manager.go
index df600cb0..2cfe553e 100644
--- a/app/protocol/manager.go
+++ b/app/protocol/manager.go
@@ -115,12 +115,6 @@ func (m *Manager) SetOnTransactionAddedToMempoolHandler(onTransactionAddedToMemp
 	m.context.SetOnTransactionAddedToMempoolHandler(onTransactionAddedToMempoolHandler)
 }
 
-// ShouldMine returns whether it's ok to use block template from this node
-// for mining purposes.
-func (m *Manager) ShouldMine() (bool, error) {
-	return m.context.IsNearlySynced()
-}
-
 // IsIBDRunning returns true if IBD is currently marked as running
 func (m *Manager) IsIBDRunning() bool {
 	return m.context.IsIBDRunning()
diff --git a/app/rpc/rpchandlers/get_block_template.go b/app/rpc/rpchandlers/get_block_template.go
index f80bae5a..2ae3f882 100644
--- a/app/rpc/rpchandlers/get_block_template.go
+++ b/app/rpc/rpchandlers/get_block_template.go
@@ -28,7 +28,8 @@ func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, reque
 	}
 
 	coinbaseData := &externalapi.DomainCoinbaseData{ScriptPublicKey: scriptPublicKey, ExtraData: []byte(version.Version() + "/" + getBlockTemplateRequest.ExtraData)}
-	templateBlock, err := context.Domain.MiningManager().GetBlockTemplate(coinbaseData)
+
+	templateBlock, isNearlySynced, err := context.Domain.MiningManager().GetBlockTemplate(coinbaseData)
 	if err != nil {
 		return nil, err
 	}
@@ -41,10 +42,5 @@ func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, reque
 
 	rpcBlock := appmessage.DomainBlockToRPCBlock(templateBlock)
 
-	isSynced, err := context.ProtocolManager.ShouldMine()
-	if err != nil {
-		return nil, err
-	}
-
-	return appmessage.NewGetBlockTemplateResponseMessage(rpcBlock, isSynced), nil
+	return appmessage.NewGetBlockTemplateResponseMessage(rpcBlock, context.ProtocolManager.Context().HasPeers() && isNearlySynced), nil
 }
diff --git a/app/rpc/rpchandlers/submit_block.go b/app/rpc/rpchandlers/submit_block.go
index 36bd9b3b..3a675a56 100644
--- a/app/rpc/rpchandlers/submit_block.go
+++ b/app/rpc/rpchandlers/submit_block.go
@@ -15,9 +15,14 @@ import (
 func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
 	submitBlockRequest := request.(*appmessage.SubmitBlockRequestMessage)
 
-	isSynced, err := context.ProtocolManager.ShouldMine()
-	if err != nil {
-		return nil, err
+	var err error
+	isSynced := false
+	// The node is considered synced if it has peers and consensus state is nearly synced
+	if context.ProtocolManager.Context().HasPeers() {
+		isSynced, err = context.ProtocolManager.Context().IsNearlySynced()
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	if !context.Config.AllowSubmitBlockWhenNotSynced && !isSynced {
diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go
index c11a5511..46aeda7a 100644
--- a/domain/consensus/consensus.go
+++ b/domain/consensus/consensus.go
@@ -1,6 +1,7 @@
 package consensus
 
 import (
+	"github.com/kaspanet/kaspad/util/mstime"
 	"math/big"
 	"sync"
 
@@ -20,6 +21,8 @@ type consensus struct {
 	genesisBlock *externalapi.DomainBlock
 	genesisHash  *externalapi.DomainHash
 
+	expectedDAAWindowDurationInMilliseconds int64
+
 	blockProcessor        model.BlockProcessor
 	blockBuilder          model.BlockBuilder
 	consensusStateManager model.ConsensusStateManager
@@ -154,15 +157,31 @@ func (s *consensus) BuildBlock(coinbaseData *externalapi.DomainCoinbaseData,
 	return block, err
 }
 
-// BuildBlockWithTemplateMetadata builds a block over the current state, with the transactions
-// selected by the given transactionSelector plus metadata information related to coinbase rewards
-func (s *consensus) BuildBlockWithTemplateMetadata(coinbaseData *externalapi.DomainCoinbaseData,
-	transactions []*externalapi.DomainTransaction) (block *externalapi.DomainBlock, coinbaseHasRedReward bool, err error) {
+// BuildBlockTemplate builds a block over the current state, with the transactions
+// selected by the given transactionSelector plus metadata information related to
+// coinbase rewards and node sync status
+func (s *consensus) BuildBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData,
+	transactions []*externalapi.DomainTransaction) (*externalapi.DomainBlockTemplate, error) {
 
 	s.lock.Lock()
 	defer s.lock.Unlock()
 
-	return s.blockBuilder.BuildBlock(coinbaseData, transactions)
+	block, hasRedReward, err := s.blockBuilder.BuildBlock(coinbaseData, transactions)
+	if err != nil {
+		return nil, err
+	}
+
+	isNearlySynced, err := s.isNearlySyncedNoLock()
+	if err != nil {
+		return nil, err
+	}
+
+	return &externalapi.DomainBlockTemplate{
+		Block:                block,
+		CoinbaseData:         coinbaseData,
+		CoinbaseHasRedReward: hasRedReward,
+		IsNearlySynced:       isNearlySynced,
+	}, nil
 }
 
 // ValidateAndInsertBlock validates the given block and, if valid, applies it
@@ -894,3 +913,42 @@ func (s *consensus) VirtualMergeDepthRoot() (*externalapi.DomainHash, error) {
 	stagingArea := model.NewStagingArea()
 	return s.mergeDepthManager.VirtualMergeDepthRoot(stagingArea)
 }
+
+// IsNearlySynced returns whether this consensus is considered synced or close to being synced. This info
+// is used to determine if it's ok to use a block template from this node for mining purposes.
+func (s *consensus) IsNearlySynced() (bool, error) {
+	s.lock.Lock()
+	defer s.lock.Unlock()
+
+	return s.isNearlySyncedNoLock()
+}
+
+func (s *consensus) isNearlySyncedNoLock() (bool, error) {
+	stagingArea := model.NewStagingArea()
+	virtualGHOSTDAGData, err := s.ghostdagDataStores[0].Get(s.databaseContext, stagingArea, model.VirtualBlockHash, false)
+	if err != nil {
+		return false, err
+	}
+
+	if virtualGHOSTDAGData.SelectedParent().Equal(s.genesisHash) {
+		return false, nil
+	}
+
+	virtualSelectedParentHeader, err := s.blockHeaderStore.BlockHeader(s.databaseContext, stagingArea, virtualGHOSTDAGData.SelectedParent())
+	if err != nil {
+		return false, err
+	}
+
+	now := mstime.Now().UnixMilliseconds()
+	// As a heuristic, we allow the node to mine if he is likely to be within the current DAA window of fully synced nodes.
+	// Such blocks contribute to security by maintaining the current difficulty despite possibly being slightly out of sync.
+	if now-virtualSelectedParentHeader.TimeInMilliseconds() < s.expectedDAAWindowDurationInMilliseconds {
+		log.Debugf("The selected tip timestamp is recent (%d), so IsNearlySynced returns true",
+			virtualSelectedParentHeader.TimeInMilliseconds())
+		return true, nil
+	}
+
+	log.Debugf("The selected tip timestamp is old (%d), so IsNearlySynced returns false",
+		virtualSelectedParentHeader.TimeInMilliseconds())
+	return false, nil
+}
diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go
index 020e41d5..59356721 100644
--- a/domain/consensus/factory.go
+++ b/domain/consensus/factory.go
@@ -472,6 +472,9 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
 		genesisBlock: config.GenesisBlock,
 		genesisHash:  config.GenesisHash,
 
+		expectedDAAWindowDurationInMilliseconds: config.TargetTimePerBlock.Milliseconds() *
+			int64(config.DifficultyAdjustmentWindowSize),
+
 		blockProcessor:        blockProcessor,
 		blockBuilder:          blockBuilder,
 		consensusStateManager: consensusStateManager,
diff --git a/domain/consensus/model/externalapi/blocktemplate.go b/domain/consensus/model/externalapi/blocktemplate.go
index 731812c1..5456ef2d 100644
--- a/domain/consensus/model/externalapi/blocktemplate.go
+++ b/domain/consensus/model/externalapi/blocktemplate.go
@@ -5,6 +5,7 @@ type DomainBlockTemplate struct {
 	Block                *DomainBlock
 	CoinbaseData         *DomainCoinbaseData
 	CoinbaseHasRedReward bool
+	IsNearlySynced       bool
 }
 
 // Clone returns a clone of DomainBlockTemplate
@@ -13,5 +14,6 @@ func (bt *DomainBlockTemplate) Clone() *DomainBlockTemplate {
 		Block:                bt.Block.Clone(),
 		CoinbaseData:         bt.CoinbaseData.Clone(),
 		CoinbaseHasRedReward: bt.CoinbaseHasRedReward,
+		IsNearlySynced:       bt.IsNearlySynced,
 	}
 }
diff --git a/domain/consensus/model/externalapi/consensus.go b/domain/consensus/model/externalapi/consensus.go
index 0e6328f8..397113b6 100644
--- a/domain/consensus/model/externalapi/consensus.go
+++ b/domain/consensus/model/externalapi/consensus.go
@@ -4,7 +4,7 @@ package externalapi
 type Consensus interface {
 	Init(skipAddingGenesis bool) error
 	BuildBlock(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlock, error)
-	BuildBlockWithTemplateMetadata(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (block *DomainBlock, coinbaseHasRedReward bool, err error)
+	BuildBlockTemplate(coinbaseData *DomainCoinbaseData, transactions []*DomainTransaction) (*DomainBlockTemplate, error)
 	ValidateAndInsertBlock(block *DomainBlock, shouldValidateAgainstUTXO bool) (*VirtualChangeSet, error)
 	ValidateAndInsertBlockWithTrustedData(block *BlockWithTrustedData, validateUTXO bool) (*VirtualChangeSet, error)
 	ValidateTransactionAndPopulateWithConsensusData(transaction *DomainTransaction) error
@@ -54,4 +54,5 @@ type Consensus interface {
 	TrustedGHOSTDAGData(blockHash *DomainHash) (*BlockGHOSTDAGData, error)
 	IsChainBlock(blockHash *DomainHash) (bool, error)
 	VirtualMergeDepthRoot() (*DomainHash, error)
+	IsNearlySynced() (bool, error)
 }
diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
index 39beaf21..564542db 100644
--- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
+++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go
@@ -142,7 +142,7 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
 		len(candidateTxs))
 
 	blockTxs := btb.selectTransactions(candidateTxs)
-	blk, coinbaseHasRedReward, err := btb.consensusReference.Consensus().BuildBlockWithTemplateMetadata(coinbaseData, blockTxs.selectedTxs)
+	blockTemplate, err := btb.consensusReference.Consensus().BuildBlockTemplate(coinbaseData, blockTxs.selectedTxs)
 
 	invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
 	if errors.As(err, &invalidTxsErr) {
@@ -167,13 +167,9 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
 	}
 
 	log.Debugf("Created new block template (%d transactions, %d in fees, %d mass, target difficulty %064x)",
-		len(blk.Transactions), blockTxs.totalFees, blockTxs.totalMass, difficulty.CompactToBig(blk.Header.Bits()))
+		len(blockTemplate.Block.Transactions), blockTxs.totalFees, blockTxs.totalMass, difficulty.CompactToBig(blockTemplate.Block.Header.Bits()))
 
-	return &consensusexternalapi.DomainBlockTemplate{
-		Block:                blk,
-		CoinbaseData:         coinbaseData,
-		CoinbaseHasRedReward: coinbaseHasRedReward,
-	}, nil
+	return blockTemplate, nil
 }
 
 // ModifyBlockTemplate modifies an existing block template to the requested coinbase data and updates the timestamp
diff --git a/domain/miningmanager/factory.go b/domain/miningmanager/factory.go
index 9f304b8c..16b50fb2 100644
--- a/domain/miningmanager/factory.go
+++ b/domain/miningmanager/factory.go
@@ -27,7 +27,7 @@ func (f *factory) NewMiningManager(consensusReference consensusreference.Consens
 		consensusReference:   consensusReference,
 		mempool:              mempool,
 		blockTemplateBuilder: blockTemplateBuilder,
-		cachingTime:          time.Now(),
+		cachingTime:          time.Time{},
 		cacheLock:            &sync.Mutex{},
 	}
 }
diff --git a/domain/miningmanager/miningmanager.go b/domain/miningmanager/miningmanager.go
index 5534d7ce..1555ff2e 100644
--- a/domain/miningmanager/miningmanager.go
+++ b/domain/miningmanager/miningmanager.go
@@ -4,7 +4,6 @@ import (
 	"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
 	"github.com/kaspanet/kaspad/domain/consensusreference"
 	miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model"
-	"github.com/kaspanet/kaspad/util/mstime"
 	"sync"
 	"time"
 )
@@ -12,7 +11,8 @@ import (
 // MiningManager creates block templates for mining as well as maintaining
 // known transactions that have no yet been added to any block
 type MiningManager interface {
-	GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainBlock, error)
+	GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (block *externalapi.DomainBlock, isNearlySynced bool, err error)
+	ClearBlockTemplate()
 	GetBlockTemplateBuilder() miningmanagermodel.BlockTemplateBuilder
 	GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool)
 	AllTransactions() []*externalapi.DomainTransaction
@@ -33,57 +33,45 @@ type miningManager struct {
 }
 
 // GetBlockTemplate obtains a block template for a miner to consume
-func (mm *miningManager) GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainBlock, error) {
+func (mm *miningManager) GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (block *externalapi.DomainBlock, isNearlySynced bool, err error) {
+	mm.cacheLock.Lock()
 	immutableCachedTemplate := mm.getImmutableCachedTemplate()
 	// We first try and use a cached template
 	if immutableCachedTemplate != nil {
-		virtualInfo, err := mm.consensusReference.Consensus().GetVirtualInfo()
-		if err != nil {
-			return nil, err
+		mm.cacheLock.Unlock()
+		if immutableCachedTemplate.CoinbaseData.Equal(coinbaseData) {
+			return immutableCachedTemplate.Block, immutableCachedTemplate.IsNearlySynced, nil
 		}
-		if externalapi.HashesEqual(virtualInfo.ParentHashes, immutableCachedTemplate.Block.Header.DirectParents()) {
-			if immutableCachedTemplate.CoinbaseData.Equal(coinbaseData) {
-				// Both, virtual parents and coinbase data are equal, simply return the cached block with updated time
-				newTimestamp := mstime.Now().UnixMilliseconds()
-				if newTimestamp < immutableCachedTemplate.Block.Header.TimeInMilliseconds() {
-					// Keep the previous time as built by internal consensus median time logic
-					return immutableCachedTemplate.Block, nil
-				}
-				// If new time stamp is later than current, update the header
-				mutableHeader := immutableCachedTemplate.Block.Header.ToMutable()
-				mutableHeader.SetTimeInMilliseconds(newTimestamp)
-
-				return &externalapi.DomainBlock{
-					Header:       mutableHeader.ToImmutable(),
-					Transactions: immutableCachedTemplate.Block.Transactions,
-				}, nil
-			}
-
-			// Virtual parents are equal, but coinbase data is new -- make the minimum changes required
-			// Note we first clone the block template since it is modified by the call
-			modifiedBlockTemplate, err := mm.blockTemplateBuilder.ModifyBlockTemplate(coinbaseData, immutableCachedTemplate.Clone())
-			if err != nil {
-				return nil, err
-			}
-
-			// No point in updating cache since we have no reason to believe this coinbase will be used more
-			// than the previous one, and we want to maintain the original template caching time
-			return modifiedBlockTemplate.Block, nil
+		// Coinbase data is new -- make the minimum changes required
+		// Note we first clone the block template since it is modified by the call
+		modifiedBlockTemplate, err := mm.blockTemplateBuilder.ModifyBlockTemplate(coinbaseData, immutableCachedTemplate.Clone())
+		if err != nil {
+			return nil, false, err
 		}
+
+		// No point in updating cache since we have no reason to believe this coinbase will be used more
+		// than the previous one, and we want to maintain the original template caching time
+		return modifiedBlockTemplate.Block, modifiedBlockTemplate.IsNearlySynced, nil
 	}
+	defer mm.cacheLock.Unlock()
 	// No relevant cache, build a template
 	blockTemplate, err := mm.blockTemplateBuilder.BuildBlockTemplate(coinbaseData)
 	if err != nil {
-		return nil, err
+		return nil, false, err
 	}
 	// Cache the built template
 	mm.setImmutableCachedTemplate(blockTemplate)
-	return blockTemplate.Block, nil
+	return blockTemplate.Block, blockTemplate.IsNearlySynced, nil
 }
 
-func (mm *miningManager) getImmutableCachedTemplate() *externalapi.DomainBlockTemplate {
+func (mm *miningManager) ClearBlockTemplate() {
 	mm.cacheLock.Lock()
-	defer mm.cacheLock.Unlock()
+	mm.cachingTime = time.Time{}
+	mm.cachedBlockTemplate = nil
+	mm.cacheLock.Unlock()
+}
+
+func (mm *miningManager) getImmutableCachedTemplate() *externalapi.DomainBlockTemplate {
 	if time.Since(mm.cachingTime) > time.Second {
 		// No point in cache optimizations if queries are more than a second apart -- we prefer rechecking the mempool.
 		// Full explanation: On the one hand this is a sub-millisecond optimization, so there is no harm in doing the full block building
@@ -95,8 +83,6 @@ func (mm *miningManager) getImmutableCachedTemplate() *externalapi.DomainBlockTe
 }
 
 func (mm *miningManager) setImmutableCachedTemplate(blockTemplate *externalapi.DomainBlockTemplate) {
-	mm.cacheLock.Lock()
-	defer mm.cacheLock.Unlock()
 	mm.cachingTime = time.Now()
 	mm.cachedBlockTemplate = blockTemplate
 }


Test patch:
diff --git a/domain/miningmanager/miningmanager_test.go b/domain/miningmanager/miningmanager_test.go
index 4227b7f9..d8a31aed 100644
--- a/domain/miningmanager/miningmanager_test.go
+++ b/domain/miningmanager/miningmanager_test.go
@@ -310,7 +310,7 @@ func TestOrphanTransactions(t *testing.T) {
 			}
 		}
 
-		block, err := miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
+		block, _, err := miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
 			ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
 			ExtraData:       nil})
 		if err != nil {
@@ -356,7 +356,7 @@ func TestOrphanTransactions(t *testing.T) {
 					"oprhan anymore.", consensushashing.TransactionID(transaction))
 			}
 		}
-		block, err = miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
+		block, _, err = miningManager.GetBlockTemplate(&externalapi.DomainCoinbaseData{
 			ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
 			ExtraData:       nil})
 		if err != nil {
@@ -615,7 +615,7 @@ func TestModifyBlockTemplate(t *testing.T) {
 		emptyCoinbaseData := &externalapi.DomainCoinbaseData{
 			ScriptPublicKey: &externalapi.ScriptPublicKey{Script: nil, Version: 0},
 			ExtraData:       nil}
-		block, err := miningManager.GetBlockTemplate(emptyCoinbaseData)
+		block, _, err := miningManager.GetBlockTemplate(emptyCoinbaseData)
 		if err != nil {
 			t.Fatalf("Failed get a block template: %v", err)
 		}
@@ -665,7 +665,7 @@ func TestModifyBlockTemplate(t *testing.T) {
 					"oprhan anymore.", consensushashing.TransactionID(transaction))
 			}
 		}
-		block, err = miningManager.GetBlockTemplate(emptyCoinbaseData)
+		block, _, err = miningManager.GetBlockTemplate(emptyCoinbaseData)
 		if err != nil {
 			t.Fatalf("GetBlockTemplate: %v", err)
 		}