github.com/onflow/flow-go@v0.33.17/consensus/hotstuff/cruisectl/proposal_timing.go (about)

     1  package cruisectl
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/onflow/flow-go/consensus/hotstuff"
     7  	"github.com/onflow/flow-go/model/flow"
     8  )
     9  
    10  // ProposalTiming encapsulates the output of the BlockTimeController. On the happy path,
    11  // the controller observes a block and generates a specific ProposalTiming in response.
    12  // For the happy path, the ProposalTiming describes when the child proposal should be
    13  // broadcast.
    14  // However, observations other than blocks might also be used to instantiate ProposalTiming
    15  // objects, e.g. controller instantiation, a disabled controller, etc.
    16  // The purpose of ProposalTiming is to convert the controller output to timing information
    17  // that the EventHandler understands. By convention, ProposalTiming should be treated as
    18  // immutable.
    19  type ProposalTiming interface {
    20  	hotstuff.ProposalDurationProvider
    21  
    22  	// ObservationView returns the view of the observation that the controller
    23  	// processed and generated this ProposalTiming instance in response.
    24  	ObservationView() uint64
    25  
    26  	// ObservationTime returns the time, when the controller received the
    27  	// leading to the generation of this ProposalTiming instance.
    28  	ObservationTime() time.Time
    29  }
    30  
    31  /* *************************************** publishImmediately *************************************** */
    32  
    33  // publishImmediately implements ProposalTiming: it returns the time when the view
    34  // was entered as the TargetPublicationTime. By convention, publishImmediately should
    35  // be treated as immutable.
    36  type publishImmediately struct {
    37  	observationView uint64
    38  	observationTime time.Time
    39  }
    40  
    41  var _ ProposalTiming = (*publishImmediately)(nil)
    42  
    43  func newPublishImmediately(observationView uint64, observationTime time.Time) *publishImmediately {
    44  	return &publishImmediately{
    45  		observationView: observationView,
    46  		observationTime: observationTime,
    47  	}
    48  }
    49  
    50  func (pt *publishImmediately) TargetPublicationTime(_ uint64, timeViewEntered time.Time, _ flow.Identifier) time.Time {
    51  	return timeViewEntered
    52  }
    53  func (pt *publishImmediately) ObservationView() uint64             { return pt.observationView }
    54  func (pt *publishImmediately) ObservationTime() time.Time          { return pt.observationTime }
    55  func (pt *publishImmediately) ConstrainedBlockTime() time.Duration { return 0 }
    56  
    57  /* *************************************** happyPathBlockTime *************************************** */
    58  
    59  // happyPathBlockTime implements ProposalTiming for the happy path. Here, `TimedBlock` _latest_ block that the
    60  // controller observed, and the `unconstrainedBlockTime` for the _child_ of this block.
    61  // This function internally holds the _unconstrained_ view duration as computed by the BlockTimeController. Caution,
    62  // no limits of authority have been applied to this value yet. The final controller output satisfying the limits of
    63  // authority is computed by function `ConstrainedBlockTime()`
    64  //
    65  // For a given view where we are the primary, suppose the parent block we are building on top of has been observed
    66  // at time `t := TimedBlock.TimeObserved` and applying the limits of authority yields `d := ConstrainedBlockTime()`
    67  // Then, `TargetPublicationTime(..)` returns `t + d` as the target publication time for the child block.
    68  //
    69  // By convention, happyPathBlockTime should be treated as immutable.
    70  // TODO: any additional logic for assiting the EventHandler in determining the applied delay should be added to the ControllerViewDuration
    71  type happyPathBlockTime struct {
    72  	TimedBlock   // latest block observed by the controller, including the time stamp when the controller received the block [UTC]
    73  	TimingConfig // timing configuration for the controller, for retrieving the controller's limits of authority
    74  
    75  	// unconstrainedBlockTime is the delay, relative to `TimedBlock.TimeObserved` when the controller would
    76  	// like the child block to be published. Caution, no limits of authority have been applied to this value yet.
    77  	// The final controller output after applying the limits of authority is returned by function `ConstrainedBlockTime`
    78  	unconstrainedBlockTime time.Duration // desired duration until releasing the child block, measured from `TimedBlock.TimeObserved`
    79  
    80  	constrainedBlockTime time.Duration // block time _after_ applying limits of authority to unconstrainedBlockTime
    81  }
    82  
    83  var _ ProposalTiming = (*happyPathBlockTime)(nil)
    84  
    85  // newHappyPathBlockTime instantiates a new happyPathBlockTime
    86  func newHappyPathBlockTime(timedBlock TimedBlock, unconstrainedBlockTime time.Duration, timingConfig TimingConfig) *happyPathBlockTime {
    87  	return &happyPathBlockTime{
    88  		TimingConfig:           timingConfig,
    89  		TimedBlock:             timedBlock,
    90  		unconstrainedBlockTime: unconstrainedBlockTime,
    91  		constrainedBlockTime:   min(max(unconstrainedBlockTime, timingConfig.MinViewDuration.Load()), timingConfig.MaxViewDuration.Load()),
    92  	}
    93  }
    94  
    95  func (pt *happyPathBlockTime) ObservationView() uint64             { return pt.Block.View }
    96  func (pt *happyPathBlockTime) ObservationTime() time.Time          { return pt.TimeObserved }
    97  func (pt *happyPathBlockTime) ConstrainedBlockTime() time.Duration { return pt.constrainedBlockTime }
    98  
    99  // TargetPublicationTime operates in two possible modes:
   100  //  1. If `parentBlockId` matches our `TimedBlock`, i.e. the EventHandler is just building the child block, then
   101  //     we return `TimedBlock.TimeObserved + ConstrainedBlockTime` as the target publication time for the child block.
   102  //  2. If `parentBlockId` does _not_ match our `TimedBlock`, the EventHandler should release the block immediately.
   103  //     This heuristic is based on the intuition that Block time is expected to be very long when deviating from the happy path.
   104  func (pt *happyPathBlockTime) TargetPublicationTime(proposalView uint64, timeViewEntered time.Time, parentBlockId flow.Identifier) time.Time {
   105  	if parentBlockId != pt.Block.BlockID {
   106  		return timeViewEntered // broadcast immediately
   107  	}
   108  	return pt.TimeObserved.Add(pt.ConstrainedBlockTime()) // happy path
   109  }
   110  
   111  /* *************************************** fallbackTiming for EECC *************************************** */
   112  
   113  // fallbackTiming implements ProposalTiming, for the basic fallback:
   114  // function `TargetPublicationTime(..)` always returns `timeViewEntered + defaultProposalDuration`
   115  type fallbackTiming struct {
   116  	observationView         uint64
   117  	observationTime         time.Time
   118  	defaultProposalDuration time.Duration
   119  }
   120  
   121  var _ ProposalTiming = (*fallbackTiming)(nil)
   122  
   123  func newFallbackTiming(observationView uint64, observationTime time.Time, defaultProposalDuration time.Duration) *fallbackTiming {
   124  	return &fallbackTiming{
   125  		observationView:         observationView,
   126  		observationTime:         observationTime,
   127  		defaultProposalDuration: defaultProposalDuration,
   128  	}
   129  }
   130  
   131  func (pt *fallbackTiming) TargetPublicationTime(_ uint64, timeViewEntered time.Time, _ flow.Identifier) time.Time {
   132  	return timeViewEntered.Add(pt.defaultProposalDuration)
   133  }
   134  func (pt *fallbackTiming) ObservationView() uint64    { return pt.observationView }
   135  func (pt *fallbackTiming) ObservationTime() time.Time { return pt.observationTime }
   136  
   137  /* *************************************** auxiliary functions *************************************** */
   138  
   139  func min(d1, d2 time.Duration) time.Duration {
   140  	if d1 < d2 {
   141  		return d1
   142  	}
   143  	return d2
   144  }
   145  
   146  func max(d1, d2 time.Duration) time.Duration {
   147  	if d1 > d2 {
   148  		return d1
   149  	}
   150  	return d2
   151  }