github.com/cosmos/cosmos-sdk@v0.50.10/baseapp/oe/optimistic_execution.go (about) 1 package oe 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/hex" 7 "math/rand" 8 "sync" 9 "time" 10 11 abci "github.com/cometbft/cometbft/abci/types" 12 13 "cosmossdk.io/log" 14 ) 15 16 // FinalizeBlockFunc is the function that is called by the OE to finalize the 17 // block. It is the same as the one in the ABCI app. 18 type FinalizeBlockFunc func(context.Context, *abci.RequestFinalizeBlock) (*abci.ResponseFinalizeBlock, error) 19 20 // OptimisticExecution is a struct that contains the OE context. It is used to 21 // run the FinalizeBlock function in a goroutine, and to abort it if needed. 22 type OptimisticExecution struct { 23 finalizeBlockFunc FinalizeBlockFunc // ABCI FinalizeBlock function with a context 24 logger log.Logger 25 26 mtx sync.Mutex 27 stopCh chan struct{} 28 request *abci.RequestFinalizeBlock 29 response *abci.ResponseFinalizeBlock 30 err error 31 cancelFunc func() // cancel function for the context 32 initialized bool // A boolean value indicating whether the struct has been initialized 33 34 // debugging/testing options 35 abortRate int // number from 0 to 100 that determines the percentage of OE that should be aborted 36 } 37 38 // NewOptimisticExecution initializes the Optimistic Execution context but does not start it. 39 func NewOptimisticExecution(logger log.Logger, fn FinalizeBlockFunc, opts ...func(*OptimisticExecution)) *OptimisticExecution { 40 logger = logger.With(log.ModuleKey, "oe") 41 oe := &OptimisticExecution{logger: logger, finalizeBlockFunc: fn} 42 for _, opt := range opts { 43 opt(oe) 44 } 45 return oe 46 } 47 48 // WithAbortRate sets the abort rate for the OE. The abort rate is a number from 49 // 0 to 100 that determines the percentage of OE that should be aborted. 50 // This is for testing purposes only and must not be used in production. 51 func WithAbortRate(rate int) func(*OptimisticExecution) { 52 return func(oe *OptimisticExecution) { 53 oe.abortRate = rate 54 } 55 } 56 57 // Reset resets the OE context. Must be called whenever we want to invalidate 58 // the current OE. 59 func (oe *OptimisticExecution) Reset() { 60 oe.mtx.Lock() 61 defer oe.mtx.Unlock() 62 oe.request = nil 63 oe.response = nil 64 oe.err = nil 65 oe.initialized = false 66 } 67 68 func (oe *OptimisticExecution) Enabled() bool { 69 return oe != nil 70 } 71 72 // Initialized returns true if the OE was initialized, meaning that it contains 73 // a request and it was run or it is running. 74 func (oe *OptimisticExecution) Initialized() bool { 75 if oe == nil { 76 return false 77 } 78 oe.mtx.Lock() 79 defer oe.mtx.Unlock() 80 81 return oe.initialized 82 } 83 84 // Execute initializes the OE and starts it in a goroutine. 85 func (oe *OptimisticExecution) Execute(req *abci.RequestProcessProposal) { 86 oe.mtx.Lock() 87 defer oe.mtx.Unlock() 88 89 oe.stopCh = make(chan struct{}) 90 oe.request = &abci.RequestFinalizeBlock{ 91 Txs: req.Txs, 92 DecidedLastCommit: req.ProposedLastCommit, 93 Misbehavior: req.Misbehavior, 94 Hash: req.Hash, 95 Height: req.Height, 96 Time: req.Time, 97 NextValidatorsHash: req.NextValidatorsHash, 98 ProposerAddress: req.ProposerAddress, 99 } 100 101 oe.logger.Debug("OE started", "height", req.Height, "hash", hex.EncodeToString(req.Hash), "time", req.Time.String()) 102 ctx, cancel := context.WithCancel(context.Background()) 103 oe.cancelFunc = cancel 104 oe.initialized = true 105 106 go func() { 107 start := time.Now() 108 resp, err := oe.finalizeBlockFunc(ctx, oe.request) 109 110 oe.mtx.Lock() 111 112 executionTime := time.Since(start) 113 oe.logger.Debug("OE finished", "duration", executionTime.String(), "height", oe.request.Height, "hash", hex.EncodeToString(oe.request.Hash)) 114 oe.response, oe.err = resp, err 115 116 close(oe.stopCh) 117 oe.mtx.Unlock() 118 }() 119 } 120 121 // AbortIfNeeded aborts the OE if the request hash is not the same as the one in 122 // the running OE. Returns true if the OE was aborted. 123 func (oe *OptimisticExecution) AbortIfNeeded(reqHash []byte) bool { 124 if oe == nil { 125 return false 126 } 127 128 oe.mtx.Lock() 129 defer oe.mtx.Unlock() 130 131 if !bytes.Equal(oe.request.Hash, reqHash) { 132 oe.logger.Error("OE aborted due to hash mismatch", "oe_hash", hex.EncodeToString(oe.request.Hash), "req_hash", hex.EncodeToString(reqHash), "oe_height", oe.request.Height, "req_height", oe.request.Height) 133 oe.cancelFunc() 134 return true 135 } else if oe.abortRate > 0 && rand.Intn(100) < oe.abortRate { 136 // this is for test purposes only, we can emulate a certain percentage of 137 // OE needed to be aborted. 138 oe.cancelFunc() 139 oe.logger.Error("OE aborted due to test abort rate") 140 return true 141 } 142 143 return false 144 } 145 146 // Abort aborts the OE unconditionally and waits for it to finish. 147 func (oe *OptimisticExecution) Abort() { 148 if oe == nil || oe.cancelFunc == nil { 149 return 150 } 151 152 oe.cancelFunc() 153 <-oe.stopCh 154 } 155 156 // WaitResult waits for the OE to finish and returns the result. 157 func (oe *OptimisticExecution) WaitResult() (*abci.ResponseFinalizeBlock, error) { 158 <-oe.stopCh 159 return oe.response, oe.err 160 }