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 }