9494 // MintNewBlock creates a new block with given actions
9595 // Note: the coinbase transfer will be added to the given transfers when minting a new block
9696 MintNewBlock (timestamp time.Time ) (* block.Block , error )
97+ // PrepareBlock start to prepares a new block with given previous hash asynchrously
98+ // it can speed up the MintNewBlock
99+ PrepareBlock (height uint64 , prevHash []byte , timestamp time.Time ) error
97100 // CommitBlock validates and appends a block to the chain
98101 CommitBlock (blk * block.Block ) error
99102 // ValidateBlock validates a new block before adding it to the blockchain
@@ -110,6 +113,7 @@ type (
110113 BlockBuilderFactory interface {
111114 // NewBlockBuilder creates block builder
112115 NewBlockBuilder (context.Context , func (action.Envelope ) (* action.SealedEnvelope , error )) (* block.Builder , error )
116+ NewBlockBuilderAt (context.Context , func (action.Envelope ) (* action.SealedEnvelope , error ), []byte ) (* block.Builder , error )
113117 }
114118
115119 // blockchain implements the Blockchain interface
@@ -125,7 +129,14 @@ type (
125129 timerFactory * prometheustimer.TimerFactory
126130
127131 // used by account-based model
128- bbf BlockBuilderFactory
132+ bbf BlockBuilderFactory
133+ draftBlocks map [hash.Hash256 ]chan * mintResult
134+ draftMutex sync.Mutex
135+ }
136+
137+ mintResult struct {
138+ blk * block.Block
139+ err error
129140 }
130141)
131142
@@ -407,6 +418,54 @@ func (bc *blockchain) context(ctx context.Context, height uint64) (context.Conte
407418 return protocol .WithFeatureWithHeightCtx (ctx ), nil
408419}
409420
421+ func (bc * blockchain ) PrepareBlock (height uint64 , prevHash []byte , timestamp time.Time ) error {
422+ ctx , err := bc .context (context .Background (), height - 1 )
423+ if err != nil {
424+ return err
425+ }
426+ tip := protocol .MustGetBlockchainCtx (ctx ).Tip
427+ ctx = bc .contextWithBlock (ctx , bc .config .ProducerAddress (), height , timestamp , protocol .CalcBaseFee (genesis .MustExtractGenesisContext (ctx ).Blockchain , & tip ), protocol .CalcExcessBlobGas (tip .ExcessBlobGas , tip .BlobGasUsed ))
428+ ctx = protocol .WithFeatureCtx (ctx )
429+ // run execution and update state trie root hash
430+ minterPrivateKey := bc .config .ProducerPrivateKey ()
431+
432+ bc .draftMutex .Lock ()
433+ bc .draftBlocks [hash .BytesToHash256 (prevHash )] = make (chan * mintResult , 1 )
434+ bc .draftMutex .Unlock ()
435+ go func () {
436+ blockBuilder , err := bc .bbf .NewBlockBuilderAt (
437+ ctx ,
438+ func (elp action.Envelope ) (* action.SealedEnvelope , error ) {
439+ return action .Sign (elp , minterPrivateKey )
440+ },
441+ prevHash ,
442+ )
443+ res := & mintResult {}
444+ defer func () {
445+ bc .draftMutex .Lock ()
446+ if _ , ok := bc .draftBlocks [hash .Hash256 (prevHash )]; ok {
447+ bc .draftBlocks [hash .Hash256 (prevHash )] <- res
448+ }
449+ bc .draftMutex .Unlock ()
450+ }()
451+ if err != nil {
452+ res .err = errors .Wrapf (err , "failed to create block builder at height %d" , height )
453+ return
454+ }
455+ blk , err := blockBuilder .SignAndBuild (minterPrivateKey )
456+ if err != nil {
457+ res .err = errors .Wrapf (err , "failed to create block at height %d" , height )
458+ return
459+ }
460+ res .blk = & blk
461+
462+ _blockMtc .WithLabelValues ("MintGas" ).Set (float64 (blk .GasUsed ()))
463+ _blockMtc .WithLabelValues ("MintActions" ).Set (float64 (len (blk .Actions )))
464+ }()
465+
466+ return nil
467+ }
468+
410469func (bc * blockchain ) MintNewBlock (timestamp time.Time ) (* block.Block , error ) {
411470 bc .mu .RLock ()
412471 defer bc .mu .RUnlock ()
@@ -422,6 +481,33 @@ func (bc *blockchain) MintNewBlock(timestamp time.Time) (*block.Block, error) {
422481 return nil , err
423482 }
424483 tip := protocol .MustGetBlockchainCtx (ctx ).Tip
484+
485+ // retrieve the draft block if it's prepared
486+ pblk , err := func () (* block.Block , error ) {
487+ bc .draftMutex .Lock ()
488+ defer bc .draftMutex .Unlock ()
489+ if ch , ok := bc .draftBlocks [tip .Hash ]; ok {
490+ // wait for the draft block
491+ res := <- ch
492+ delete (bc .draftBlocks , tip .Hash )
493+ if res .err == nil {
494+ if res .blk .Timestamp () == timestamp {
495+ return res .blk , nil
496+ }
497+ return nil , errors .Errorf ("block timestamp mismatch, expected %v, actual %v height %d" , res .blk .Timestamp (), timestamp , newblockHeight )
498+ }
499+ return nil , res .err
500+ }
501+ return nil , nil
502+ }()
503+ if pblk != nil {
504+ return pblk , nil
505+ }
506+ if err != nil {
507+ log .L ().Error ("Failed to prepare new block" , zap .Error (err ))
508+ }
509+
510+ // create a new block
425511 ctx = bc .contextWithBlock (ctx , bc .config .ProducerAddress (), newblockHeight , timestamp , protocol .CalcBaseFee (genesis .MustExtractGenesisContext (ctx ).Blockchain , & tip ), protocol .CalcExcessBlobGas (tip .ExcessBlobGas , tip .BlobGasUsed ))
426512 ctx = protocol .WithFeatureCtx (ctx )
427513 // run execution and update state trie root hash
@@ -539,6 +625,10 @@ func (bc *blockchain) commitBlock(blk *block.Block) error {
539625 _blockMtc .WithLabelValues ("blobGasUsed" ).Set (float64 (blk .BlobGasUsed ()))
540626 // emit block to all block subscribers
541627 bc .emitToSubscribers (blk )
628+
629+ bc .draftMutex .Lock ()
630+ delete (bc .draftBlocks , blk .HashBlock ())
631+ bc .draftMutex .Unlock ()
542632 return nil
543633}
544634
0 commit comments