github.com/koko1123/flow-go-1@v0.29.6/consensus/hotstuff/pacemaker/timeout/config.go (about)

     1  package timeout
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog/log"
     9  	"go.uber.org/atomic"
    10  
    11  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    12  	"github.com/koko1123/flow-go-1/module/updatable_configs"
    13  )
    14  
    15  // Config contains the configuration parameters for ExponentialIncrease-LinearDecrease
    16  // timeout.Controller
    17  //   - on timeout: increase timeout by multiplicative factor `timeoutIncrease` (user-specified)
    18  //     this results in exponential growing timeout duration on multiple subsequent timeouts
    19  //   - on progress: MULTIPLICATIVE timeout decrease
    20  type Config struct {
    21  	// ReplicaTimeout is the duration of a view before we time out [MILLISECONDS]
    22  	// ReplicaTimeout is the only variable quantity
    23  	ReplicaTimeout float64
    24  	// MinReplicaTimeout is the minimum the timeout can decrease to [MILLISECONDS]
    25  	MinReplicaTimeout float64
    26  	// VoteAggregationTimeoutFraction is the FRACTION of ReplicaTimeout which the Primary
    27  	// will maximally wait to collect enough votes before building a block (with an old qc)
    28  	VoteAggregationTimeoutFraction float64
    29  	// TimeoutDecrease: MULTIPLICATIVE factor for increasing timeout on timeout
    30  	TimeoutIncrease float64
    31  	// TimeoutDecrease: MULTIPLICATIVE factor for decreasing timeout on progress
    32  	TimeoutDecrease float64
    33  	// BlockRateDelayMS is a delay to broadcast the proposal in order to control block production rate [MILLISECONDS]
    34  	BlockRateDelayMS *atomic.Float64
    35  }
    36  
    37  var DefaultConfig = NewDefaultConfig()
    38  
    39  // NewDefaultConfig returns a default timeout configuration.
    40  // We explicitly provide a method here, which demonstrates in-code how
    41  // to compute standard values from some basic quantities.
    42  func NewDefaultConfig() Config {
    43  	// the replicas will start with 60 second time out to allow all other replicas to come online
    44  	// once the replica's views are synchronized, the timeout will decrease to more reasonable values
    45  	replicaTimeout := 60 * time.Second
    46  
    47  	// the lower bound on the replicaTimeout value
    48  	// If HotStuff is running at full speed, 1200ms should be enough. However, we add some buffer.
    49  	// This value is for instant message delivery.
    50  	minReplicaTimeout := 2 * time.Second
    51  	timeoutIncreaseFactor := 2.0
    52  	blockRateDelay := 0 * time.Millisecond
    53  
    54  	// the following demonstrates the computation of standard values
    55  	conf, err := NewConfig(
    56  		replicaTimeout,
    57  		minReplicaTimeout+blockRateDelay,
    58  		StandardVoteAggregationTimeoutFraction(minReplicaTimeout, blockRateDelay), // resulting value here is 0.5
    59  		timeoutIncreaseFactor,
    60  		StandardTimeoutDecreaseFactor(1.0/3.0, timeoutIncreaseFactor), // resulting value is 1/sqrt(2)
    61  		blockRateDelay,
    62  	)
    63  	if err != nil {
    64  		// we check in a unit test that this does not happen
    65  		panic("Default config is not compliant with timeout Config requirements")
    66  	}
    67  
    68  	return conf
    69  }
    70  
    71  // NewConfig creates a new TimoutConfig.
    72  // startReplicaTimeout: starting timeout value for replica round [Milliseconds];
    73  // minReplicaTimeout: minimal timeout value for replica round [Milliseconds];
    74  // voteAggregationTimeoutFraction: fraction of replicaTimeout which is reserved for aggregating votes;
    75  // timeoutIncrease: multiplicative factor for increasing timeout;
    76  // timeoutDecrease: linear subtrahend for timeout decrease [Milliseconds]
    77  // blockRateDelay: a delay to delay the proposal broadcasting
    78  func NewConfig(
    79  	startReplicaTimeout time.Duration,
    80  	minReplicaTimeout time.Duration,
    81  	voteAggregationTimeoutFraction float64,
    82  	timeoutIncrease float64,
    83  	timeoutDecrease float64,
    84  	blockRateDelay time.Duration,
    85  ) (Config, error) {
    86  	if startReplicaTimeout < minReplicaTimeout {
    87  		return Config{}, model.NewConfigurationErrorf("startReplicaTimeout (%dms) cannot be smaller than minReplicaTimeout (%dms)",
    88  			startReplicaTimeout.Milliseconds(), minReplicaTimeout.Milliseconds())
    89  	}
    90  	if minReplicaTimeout < 0 {
    91  		return Config{}, model.NewConfigurationErrorf("minReplicaTimeout must non-negative")
    92  	}
    93  	if voteAggregationTimeoutFraction <= 0 || 1 < voteAggregationTimeoutFraction {
    94  		return Config{}, model.NewConfigurationErrorf("VoteAggregationTimeoutFraction must be in range (0,1]")
    95  	}
    96  	if timeoutIncrease <= 1 {
    97  		return Config{}, model.NewConfigurationErrorf("TimeoutIncrease must be strictly bigger than 1")
    98  	}
    99  	if timeoutDecrease <= 0 || 1 <= timeoutDecrease {
   100  		return Config{}, model.NewConfigurationErrorf("timeoutDecrease must be in range (0,1)")
   101  	}
   102  	if err := validBlockRateDelay(blockRateDelay); err != nil {
   103  		return Config{}, err
   104  	}
   105  
   106  	tc := Config{
   107  		ReplicaTimeout:                 float64(startReplicaTimeout.Milliseconds()),
   108  		MinReplicaTimeout:              float64(minReplicaTimeout.Milliseconds()),
   109  		VoteAggregationTimeoutFraction: voteAggregationTimeoutFraction,
   110  		TimeoutIncrease:                timeoutIncrease,
   111  		TimeoutDecrease:                timeoutDecrease,
   112  		BlockRateDelayMS:               atomic.NewFloat64(float64(blockRateDelay.Milliseconds())),
   113  	}
   114  	return tc, nil
   115  }
   116  
   117  // validBlockRateDelay validates a block rate delay config.
   118  // Returns model.ConfigurationError for invalid config inputs.
   119  func validBlockRateDelay(blockRateDelay time.Duration) error {
   120  	if blockRateDelay < 0 {
   121  		return model.NewConfigurationErrorf("blockRateDelay must be must be non-negative")
   122  	}
   123  	return nil
   124  }
   125  
   126  // GetBlockRateDelay returns the block rate delay as a Duration. This is used by
   127  // the dyamic config manager.
   128  func (c *Config) GetBlockRateDelay() time.Duration {
   129  	ms := c.BlockRateDelayMS.Load()
   130  	return time.Millisecond * time.Duration(ms)
   131  }
   132  
   133  // SetBlockRateDelay sets the block rate delay. It is used to modify this config
   134  // value while HotStuff is running.
   135  // Returns updatable_configs.ValidationError if the new value is invalid.
   136  func (c *Config) SetBlockRateDelay(delay time.Duration) error {
   137  	if err := validBlockRateDelay(delay); err != nil {
   138  		if model.IsConfigurationError(err) {
   139  			return updatable_configs.NewValidationErrorf("invalid block rate delay: %w", err)
   140  		}
   141  		return fmt.Errorf("unexpected error validating block rate delay: %w", err)
   142  	}
   143  	// sanity check: log a warning if we set block rate delay above min timeout
   144  	// it is valid to want to do this, to significantly slow the block rate, but
   145  	// only in edge cases
   146  	if c.MinReplicaTimeout < float64(delay.Milliseconds()) {
   147  		log.Warn().Msgf("CAUTION: setting block rate delay to %s, above min timeout %dms - this will degrade performance!", delay.String(), int64(c.MinReplicaTimeout))
   148  	}
   149  	c.BlockRateDelayMS.Store(float64(delay.Milliseconds()))
   150  	return nil
   151  }
   152  
   153  // StandardVoteAggregationTimeoutFraction calculates a standard value for the VoteAggregationTimeoutFraction in case a block delay is used.
   154  // The motivation for the standard value is as follows:
   155  //   - the next primary receives the block it ideally would extend at some time t
   156  //   - the best guess the primary has, when other nodes would receive the block is at time t as well
   157  //   - the primary needs to get its block to the other replicas, before they time out:
   158  //     the primary uses its own timeout as estimator for the other replicas' timeout
   159  func StandardVoteAggregationTimeoutFraction(minReplicaTimeout time.Duration, blockRateDelay time.Duration) float64 {
   160  	standardVoteAggregationTimeoutFraction := 0.5
   161  	minReplicaTimeoutMS := float64(minReplicaTimeout.Milliseconds())
   162  	blockRateDelayMS := float64(blockRateDelay.Milliseconds())
   163  	return (standardVoteAggregationTimeoutFraction*minReplicaTimeoutMS + blockRateDelayMS) / (minReplicaTimeoutMS + blockRateDelayMS)
   164  }
   165  
   166  // StandardTimeoutDecreaseFactor calculates a standard value for TimeoutDecreaseFactor
   167  // for an assumed max fraction of offline (byzantine) HotStuff committee members
   168  func StandardTimeoutDecreaseFactor(maxFractionOfflineReplicas, timeoutIncreaseFactor float64) float64 {
   169  	return math.Pow(timeoutIncreaseFactor, maxFractionOfflineReplicas/(maxFractionOfflineReplicas-1))
   170  }