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 }