package contractcourt import ( "fmt" "github.com/btcsuite/btcd/btcutil" ) const ( // MinBudgetValue is the minimal budget that we allow when configuring // the budget used in sweeping outputs. The actual budget can be lower // if the user decides to NOT set this value. // // NOTE: This value is chosen so the linear fee function can increase // at least 1 sat/kw per block. MinBudgetValue btcutil.Amount = 1008 // MinBudgetRatio is the minimal ratio that we allow when configuring // the budget ratio used in sweeping outputs. MinBudgetRatio = 0.001 // DefaultBudgetRatio defines a default budget ratio to be used when // sweeping inputs. This is a large value, which is fine as the final // fee rate is capped at the max fee rate configured. DefaultBudgetRatio = 0.5 ) // BudgetConfig is a struct that holds the configuration when offering outputs // to the sweeper. // //nolint:lll type BudgetConfig struct { ToLocal btcutil.Amount `long:"tolocal" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping the to_local output. If set, the budget calculated using the ratio (if set) will be capped at this value."` ToLocalRatio float64 `long:"tolocalratio" description:"The ratio of the value in to_local output to allocate as the budget to pay fees when sweeping it."` AnchorCPFP btcutil.Amount `long:"anchorcpfp" description:"The amount in satoshis to allocate as the budget to pay fees when CPFPing a force close tx using the anchor output. If set, the budget calculated using the ratio (if set) will be capped at this value."` AnchorCPFPRatio float64 `long:"anchorcpfpratio" description:"The ratio of a special value to allocate as the budget to pay fees when CPFPing a force close tx using the anchor output. The special value is the sum of all time-sensitive HTLCs on this commitment subtracted by their budgets."` DeadlineHTLC btcutil.Amount `long:"deadlinehtlc" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping a time-sensitive (first-level) HTLC. If set, the budget calculated using the ratio (if set) will be capped at this value."` DeadlineHTLCRatio float64 `long:"deadlinehtlcratio" description:"The ratio of the value in a time-sensitive (first-level) HTLC to allocate as the budget to pay fees when sweeping it."` NoDeadlineHTLC btcutil.Amount `long:"nodeadlinehtlc" description:"The amount in satoshis to allocate as the budget to pay fees when sweeping a non-time-sensitive (second-level) HTLC. If set, the budget calculated using the ratio (if set) will be capped at this value."` NoDeadlineHTLCRatio float64 `long:"nodeadlinehtlcratio" description:"The ratio of the value in a non-time-sensitive (second-level) HTLC to allocate as the budget to pay fees when sweeping it."` } // Validate checks the budget configuration for any invalid values. func (b *BudgetConfig) Validate() error { // Exit early if no budget config is set. if b == nil { return fmt.Errorf("no budget config set") } // Sanity check all fields. if b.ToLocal != 0 && b.ToLocal < MinBudgetValue { return fmt.Errorf("tolocal must be at least %v", MinBudgetValue) } if b.ToLocalRatio != 0 && b.ToLocalRatio < MinBudgetRatio { return fmt.Errorf("tolocalratio must be at least %v", MinBudgetRatio) } if b.AnchorCPFP != 0 && b.AnchorCPFP < MinBudgetValue { return fmt.Errorf("anchorcpfp must be at least %v", MinBudgetValue) } if b.AnchorCPFPRatio != 0 && b.AnchorCPFPRatio < MinBudgetRatio { return fmt.Errorf("anchorcpfpratio must be at least %v", MinBudgetRatio) } if b.DeadlineHTLC != 0 && b.DeadlineHTLC < MinBudgetValue { return fmt.Errorf("deadlinehtlc must be at least %v", MinBudgetValue) } if b.DeadlineHTLCRatio != 0 && b.DeadlineHTLCRatio < MinBudgetRatio { return fmt.Errorf("deadlinehtlcratio must be at least %v", MinBudgetRatio) } if b.NoDeadlineHTLC != 0 && b.NoDeadlineHTLC < MinBudgetValue { return fmt.Errorf("nodeadlinehtlc must be at least %v", MinBudgetValue) } if b.NoDeadlineHTLCRatio != 0 && b.NoDeadlineHTLCRatio < MinBudgetRatio { return fmt.Errorf("nodeadlinehtlcratio must be at least %v", MinBudgetRatio) } return nil } // String returns a human-readable description of the budget configuration. func (b *BudgetConfig) String() string { return fmt.Sprintf("tolocal=%v tolocalratio=%v anchorcpfp=%v "+ "anchorcpfpratio=%v deadlinehtlc=%v deadlinehtlcratio=%v "+ "nodeadlinehtlc=%v nodeadlinehtlcratio=%v", b.ToLocal, b.ToLocalRatio, b.AnchorCPFP, b.AnchorCPFPRatio, b.DeadlineHTLC, b.DeadlineHTLCRatio, b.NoDeadlineHTLC, b.NoDeadlineHTLCRatio) } // DefaultSweeperConfig returns the default configuration for the sweeper. func DefaultBudgetConfig() *BudgetConfig { return &BudgetConfig{ ToLocalRatio: DefaultBudgetRatio, AnchorCPFPRatio: DefaultBudgetRatio, DeadlineHTLCRatio: DefaultBudgetRatio, NoDeadlineHTLCRatio: DefaultBudgetRatio, } } // calculateBudget takes an output value, a configured ratio and budget value, // and returns the budget to use for sweeping the output. If the budget value // is set, it will be used as cap. func calculateBudget(value btcutil.Amount, ratio float64, max btcutil.Amount) btcutil.Amount { // If ratio is not set, using the default value. if ratio == 0 { ratio = DefaultBudgetRatio } budget := value.MulF64(ratio) log.Tracef("Calculated budget=%v using value=%v, ratio=%v, cap=%v", budget, value, ratio, max) if max != 0 && budget > max { log.Debugf("Calculated budget=%v is capped at %v", budget, max) return max } return budget }