github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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 assisting 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  	constrainedBlockTime time.Duration // block time _after_ applying limits of authority to unconstrainedBlockTime
    74  }
    75  
    76  var _ ProposalTiming = (*happyPathBlockTime)(nil)
    77  
    78  // newHappyPathBlockTime instantiates a new happyPathBlockTime. Inputs:
    79  //   - `timedBlock` references the _published_ block with the highest view known to this node.
    80  //     On the consensus happy path, this node may construct the child block (iff it is the primary for
    81  //     view `timedBlock.Block.View` + 1). Note that the controller determines when to publish this child.
    82  //     In other words, when primary determines at what future time to broadcast the child, the child
    83  //     has _not_ been published and the `timedBlock` references the parent on the happy path (or another
    84  //     earlier block on the unhappy path)
    85  //   - `unconstrainedBlockTime` is the delay, relative to `timedBlock.TimeObserved` when the controller would
    86  //     like the child block to be published. Caution, no limits of authority have been applied to this value yet!
    87  //   - `timingConfig` which defines the limits for authority for the controller.
    88  //
    89  // Within the constructor, we compute the block time τ on the happy path. I.e. how much later a _direct child_
    90  // of the `timedBlock` should be published (also accounting for the controller's limits of authority).
    91  func newHappyPathBlockTime(timedBlock TimedBlock, unconstrainedBlockTime time.Duration, timingConfig TimingConfig) *happyPathBlockTime {
    92  	return &happyPathBlockTime{
    93  		TimedBlock:           timedBlock,
    94  		constrainedBlockTime: min(max(unconstrainedBlockTime, timingConfig.MinViewDuration.Load()), timingConfig.MaxViewDuration.Load()),
    95  	}
    96  }
    97  
    98  func (pt *happyPathBlockTime) ObservationView() uint64             { return pt.Block.View }
    99  func (pt *happyPathBlockTime) ObservationTime() time.Time          { return pt.TimeObserved }
   100  func (pt *happyPathBlockTime) ConstrainedBlockTime() time.Duration { return pt.constrainedBlockTime }
   101  
   102  // TargetPublicationTime operates in two possible modes:
   103  //  1. If `parentBlockId` matches our `TimedBlock`, i.e. the EventHandler is just building the child block, then
   104  //     we return `TimedBlock.TimeObserved + ConstrainedBlockTime` as the target publication time for the child block.
   105  //  2. If `parentBlockId` does _not_ match our `TimedBlock`, the EventHandler should release the block immediately.
   106  //     This heuristic is based on the intuition that Block time is expected to be very long when deviating from the happy path.
   107  func (pt *happyPathBlockTime) TargetPublicationTime(proposalView uint64, timeViewEntered time.Time, parentBlockId flow.Identifier) time.Time {
   108  	if parentBlockId != pt.Block.BlockID {
   109  		return timeViewEntered // broadcast immediately
   110  	}
   111  	return pt.TimeObserved.Add(pt.ConstrainedBlockTime()) // happy path
   112  }
   113  
   114  /* *************************************** fallbackTiming for EFM *************************************** */
   115  
   116  // fallbackTiming implements ProposalTiming, for the basic fallback:
   117  // function `TargetPublicationTime(..)` always returns `timeViewEntered + defaultProposalDuration`
   118  type fallbackTiming struct {
   119  	observationView         uint64
   120  	observationTime         time.Time
   121  	defaultProposalDuration time.Duration
   122  }
   123  
   124  var _ ProposalTiming = (*fallbackTiming)(nil)
   125  
   126  func newFallbackTiming(observationView uint64, observationTime time.Time, defaultProposalDuration time.Duration) *fallbackTiming {
   127  	return &fallbackTiming{
   128  		observationView:         observationView,
   129  		observationTime:         observationTime,
   130  		defaultProposalDuration: defaultProposalDuration,
   131  	}
   132  }
   133  
   134  func (pt *fallbackTiming) TargetPublicationTime(_ uint64, timeViewEntered time.Time, _ flow.Identifier) time.Time {
   135  	return timeViewEntered.Add(pt.defaultProposalDuration)
   136  }
   137  func (pt *fallbackTiming) ObservationView() uint64    { return pt.observationView }
   138  func (pt *fallbackTiming) ObservationTime() time.Time { return pt.observationTime }
   139  
   140  /* *************************************** auxiliary functions *************************************** */
   141  
   142  func min(d1, d2 time.Duration) time.Duration {
   143  	if d1 < d2 {
   144  		return d1
   145  	}
   146  	return d2
   147  }
   148  
   149  func max(d1, d2 time.Duration) time.Duration {
   150  	if d1 > d2 {
   151  		return d1
   152  	}
   153  	return d2
   154  }