@@ -11,7 +11,7 @@ import (
11
11
abci "github.com/cometbft/cometbft/abci/types"
12
12
abciproto "github.com/cometbft/cometbft/api/cometbft/abci/v1"
13
13
gogoproto "github.com/cosmos/gogoproto/proto"
14
- protoreflect "google.golang.org/protobuf/reflect/protoreflect"
14
+ "google.golang.org/protobuf/reflect/protoreflect"
15
15
"google.golang.org/protobuf/reflect/protoregistry"
16
16
17
17
"cosmossdk.io/collections"
@@ -28,6 +28,7 @@ import (
28
28
"cosmossdk.io/server/v2/appmanager"
29
29
"cosmossdk.io/server/v2/cometbft/handlers"
30
30
"cosmossdk.io/server/v2/cometbft/mempool"
31
+ "cosmossdk.io/server/v2/cometbft/oe"
31
32
"cosmossdk.io/server/v2/cometbft/types"
32
33
cometerrors "cosmossdk.io/server/v2/cometbft/types/errors"
33
34
"cosmossdk.io/server/v2/streaming"
@@ -77,6 +78,11 @@ type consensus[T transaction.Tx] struct {
77
78
extendVote handlers.ExtendVoteHandler
78
79
checkTxHandler handlers.CheckTxHandler [T ]
79
80
81
+ // optimisticExec contains the context required for Optimistic Execution,
82
+ // including the goroutine handling.This is experimental and must be enabled
83
+ // by developers.
84
+ optimisticExec * oe.OptimisticExecution [T ]
85
+
80
86
addrPeerFilter types.PeerFilter // filter peers by address and port
81
87
idPeerFilter types.PeerFilter // filter peers by node ID
82
88
@@ -385,6 +391,14 @@ func (c *consensus[T]) PrepareProposal(
385
391
return nil , errors .New ("no prepare proposal function was set" )
386
392
}
387
393
394
+ // Abort any running OE so it cannot overlap with `PrepareProposal`. This could happen if optimistic
395
+ // `internalFinalizeBlock` from previous round takes a long time, but consensus has moved on to next round.
396
+ // Overlap is undesirable, since `internalFinalizeBlock` and `PrepareProoposal` could share access to
397
+ // in-memory structs depending on application implementation.
398
+ // No-op if OE is not enabled.
399
+ // Similar call to Abort() is done in `ProcessProposal`.
400
+ c .optimisticExec .Abort ()
401
+
388
402
ciCtx := contextWithCometInfo (ctx , comet.Info {
389
403
Evidence : toCoreEvidence (req .Misbehavior ),
390
404
ValidatorsHash : req .NextValidatorsHash ,
@@ -421,6 +435,16 @@ func (c *consensus[T]) ProcessProposal(
421
435
return nil , errors .New ("no process proposal function was set" )
422
436
}
423
437
438
+ // Since the application can get access to FinalizeBlock state and write to it,
439
+ // we must be sure to reset it in case ProcessProposal timeouts and is called
440
+ // again in a subsequent round. However, we only want to do this after we've
441
+ // processed the first block, as we want to avoid overwriting the finalizeState
442
+ // after state changes during InitChain.
443
+ if req .Height > int64 (c .initialHeight ) {
444
+ // abort any running OE
445
+ c .optimisticExec .Abort ()
446
+ }
447
+
424
448
ciCtx := contextWithCometInfo (ctx , comet.Info {
425
449
Evidence : toCoreEvidence (req .Misbehavior ),
426
450
ValidatorsHash : req .NextValidatorsHash ,
@@ -436,6 +460,17 @@ func (c *consensus[T]) ProcessProposal(
436
460
}, nil
437
461
}
438
462
463
+ // Only execute optimistic execution if the proposal is accepted, OE is
464
+ // enabled and the block height is greater than the initial height. During
465
+ // the first block we'll be carrying state from InitChain, so it would be
466
+ // impossible for us to easily revert.
467
+ // After the first block has been processed, the next blocks will get executed
468
+ // optimistically, so that when the ABCI client calls `FinalizeBlock` the app
469
+ // can have a response ready.
470
+ if req .Height > int64 (c .initialHeight ) {
471
+ c .optimisticExec .Execute (req )
472
+ }
473
+
439
474
return & abciproto.ProcessProposalResponse {
440
475
Status : abciproto .PROCESS_PROPOSAL_STATUS_ACCEPT ,
441
476
}, nil
@@ -447,46 +482,40 @@ func (c *consensus[T]) FinalizeBlock(
447
482
ctx context.Context ,
448
483
req * abciproto.FinalizeBlockRequest ,
449
484
) (* abciproto.FinalizeBlockResponse , error ) {
450
- if err := c .validateFinalizeBlockHeight (req ); err != nil {
451
- return nil , err
452
- }
453
-
454
- if err := c .checkHalt (req .Height , req .Time ); err != nil {
455
- return nil , err
456
- }
457
-
458
- // TODO(tip): can we expect some txs to not decode? if so, what we do in this case? this does not seem to be the case,
459
- // considering that prepare and process always decode txs, assuming they're the ones providing txs we should never
460
- // have a tx that fails decoding.
461
- decodedTxs , err := decodeTxs (req .Txs , c .txCodec )
462
- if err != nil {
463
- return nil , err
464
- }
485
+ var (
486
+ resp * server.BlockResponse
487
+ newState store.WriterMap
488
+ decodedTxs []T
489
+ err error
490
+ )
491
+
492
+ if c .optimisticExec .Initialized () {
493
+ // check if the hash we got is the same as the one we are executing
494
+ aborted := c .optimisticExec .AbortIfNeeded (req .Hash )
495
+
496
+ // Wait for the OE to finish, regardless of whether it was aborted or not
497
+ res , optimistErr := c .optimisticExec .WaitResult ()
498
+
499
+ if ! aborted {
500
+ if res != nil {
501
+ resp = res .Resp
502
+ newState = res .StateChanges
503
+ decodedTxs = res .DecodedTxs
504
+ }
465
505
466
- cid , err := c . store . LastCommitID ()
467
- if err != nil {
468
- return nil , err
469
- }
506
+ if optimistErr != nil {
507
+ return nil , optimistErr
508
+ }
509
+ }
470
510
471
- blockReq := & server.BlockRequest [T ]{
472
- Height : uint64 (req .Height ),
473
- Time : req .Time ,
474
- Hash : req .Hash ,
475
- AppHash : cid .Hash ,
476
- ChainId : c .chainID ,
477
- Txs : decodedTxs ,
511
+ c .optimisticExec .Reset ()
478
512
}
479
513
480
- ciCtx := contextWithCometInfo (ctx , comet.Info {
481
- Evidence : toCoreEvidence (req .Misbehavior ),
482
- ValidatorsHash : req .NextValidatorsHash ,
483
- ProposerAddress : req .ProposerAddress ,
484
- LastCommit : toCoreCommitInfo (req .DecidedLastCommit ),
485
- })
486
-
487
- resp , newState , err := c .app .DeliverBlock (ciCtx , blockReq )
488
- if err != nil {
489
- return nil , err
514
+ if resp == nil { // if we didn't run OE, run the normal finalize block
515
+ resp , newState , decodedTxs , err = c .internalFinalizeBlock (ctx , req )
516
+ if err != nil {
517
+ return nil , err
518
+ }
490
519
}
491
520
492
521
// after we get the changeset we can produce the commit hash,
@@ -531,6 +560,52 @@ func (c *consensus[T]) FinalizeBlock(
531
560
return finalizeBlockResponse (resp , cp , appHash , c .indexedEvents , c .cfg .AppTomlConfig .Trace )
532
561
}
533
562
563
+ func (c * consensus [T ]) internalFinalizeBlock (
564
+ ctx context.Context ,
565
+ req * abciproto.FinalizeBlockRequest ,
566
+ ) (* server.BlockResponse , store.WriterMap , []T , error ) {
567
+ if err := c .validateFinalizeBlockHeight (req ); err != nil {
568
+ return nil , nil , nil , err
569
+ }
570
+
571
+ if err := c .checkHalt (req .Height , req .Time ); err != nil {
572
+ return nil , nil , nil , err
573
+ }
574
+
575
+ // TODO(tip): can we expect some txs to not decode? if so, what we do in this case? this does not seem to be the case,
576
+ // considering that prepare and process always decode txs, assuming they're the ones providing txs we should never
577
+ // have a tx that fails decoding.
578
+ decodedTxs , err := decodeTxs (req .Txs , c .txCodec )
579
+ if err != nil {
580
+ return nil , nil , nil , err
581
+ }
582
+
583
+ cid , err := c .store .LastCommitID ()
584
+ if err != nil {
585
+ return nil , nil , nil , err
586
+ }
587
+
588
+ blockReq := & server.BlockRequest [T ]{
589
+ Height : uint64 (req .Height ),
590
+ Time : req .Time ,
591
+ Hash : req .Hash ,
592
+ AppHash : cid .Hash ,
593
+ ChainId : c .chainID ,
594
+ Txs : decodedTxs ,
595
+ }
596
+
597
+ ciCtx := contextWithCometInfo (ctx , comet.Info {
598
+ Evidence : toCoreEvidence (req .Misbehavior ),
599
+ ValidatorsHash : req .NextValidatorsHash ,
600
+ ProposerAddress : req .ProposerAddress ,
601
+ LastCommit : toCoreCommitInfo (req .DecidedLastCommit ),
602
+ })
603
+
604
+ resp , stateChanges , err := c .app .DeliverBlock (ciCtx , blockReq )
605
+
606
+ return resp , stateChanges , decodedTxs , err
607
+ }
608
+
534
609
// Commit implements types.Application.
535
610
// It is called by cometbft to notify the application that a block was committed.
536
611
func (c * consensus [T ]) Commit (ctx context.Context , _ * abciproto.CommitRequest ) (* abciproto.CommitResponse , error ) {
0 commit comments