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  }