github.com/decred/dcrlnd@v0.7.6/watchtower/wtpolicy/policy.go (about)

     1  package wtpolicy
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/decred/dcrd/dcrutil/v4"
     8  	"github.com/decred/dcrd/wire"
     9  	"github.com/decred/dcrlnd/input"
    10  	"github.com/decred/dcrlnd/lnwallet"
    11  	"github.com/decred/dcrlnd/lnwallet/chainfee"
    12  	"github.com/decred/dcrlnd/watchtower/blob"
    13  )
    14  
    15  const (
    16  	// RewardScale is the denominator applied when computing the
    17  	// proportional component for a tower's reward output. The current scale
    18  	// is in millionths.
    19  	RewardScale = 1000000
    20  
    21  	// DefaultMaxUpdates specifies the number of encrypted blobs a client
    22  	// can send to the tower in a single session.
    23  	DefaultMaxUpdates = 1024
    24  
    25  	// DefaultRewardRate specifies the fraction of the channel that the
    26  	// tower takes if it successfully sweeps a breach. The value is
    27  	// expressed in millionths of the channel capacity.
    28  	DefaultRewardRate = 10000
    29  
    30  	// TODO(decred) verify these defaults.
    31  
    32  	// DefaultSweepFeeRate specifies the fee rate used to construct justice
    33  	// transactions. The value is expressed in atoms per KByte.
    34  	DefaultSweepFeeRate = chainfee.AtomPerKByte(1e4)
    35  
    36  	// MinSweepFeeRate is the minimum sweep fee rate a client may use in
    37  	// its policy, the current value is 0.00010000 dcr/KB.
    38  	MinSweepFeeRate = chainfee.AtomPerKByte(1e4)
    39  )
    40  
    41  var (
    42  	// ErrFeeExceedsInputs signals that the total input value of breaching
    43  	// commitment txn is insufficient to cover the fees required to sweep
    44  	// it.
    45  	ErrFeeExceedsInputs = errors.New("sweep fee exceeds input value")
    46  
    47  	// ErrRewardExceedsInputs signals that the reward given to the tower (in
    48  	// addition to the transaction fees) is more than the input amount.
    49  	ErrRewardExceedsInputs = errors.New("reward amount exceeds input value")
    50  
    51  	// ErrCreatesDust signals that the session's policy would create a dust
    52  	// output for the victim.
    53  	ErrCreatesDust = errors.New("justice transaction creates dust at fee rate")
    54  
    55  	// ErrAltruistReward signals that the policy is invalid because it
    56  	// contains a non-zero RewardBase or RewardRate on an altruist policy.
    57  	ErrAltruistReward = errors.New("altruist policy has reward params")
    58  
    59  	// ErrNoMaxUpdates signals that the policy specified zero MaxUpdates.
    60  	ErrNoMaxUpdates = errors.New("max updates must be positive")
    61  
    62  	// ErrSweepFeeRateTooLow signals that the policy's fee rate is too low
    63  	// to get into the mempool during low congestion.
    64  	ErrSweepFeeRateTooLow = errors.New("sweep fee rate too low")
    65  )
    66  
    67  // DefaultPolicy returns a Policy containing the default parameters that can be
    68  // used by clients or servers.
    69  func DefaultPolicy() Policy {
    70  	return Policy{
    71  		TxPolicy: TxPolicy{
    72  			BlobType:     blob.TypeAltruistCommit,
    73  			SweepFeeRate: DefaultSweepFeeRate,
    74  		},
    75  		MaxUpdates: DefaultMaxUpdates,
    76  	}
    77  }
    78  
    79  // TxPolicy defines the negotiate parameters that determine the form of the
    80  // justice transaction for a given breached state. Thus, for any given revoked
    81  // state, an identical key will result in an identical justice transaction
    82  // (barring signatures). The parameters specify the format of encrypted blobs
    83  // sent to the tower, the reward schedule for the tower, and the number of
    84  // encrypted blobs a client can send in one session.
    85  type TxPolicy struct {
    86  	// BlobType specifies the blob format that must be used by all updates sent
    87  	// under the session key used to negotiate this session.
    88  	BlobType blob.Type
    89  
    90  	// RewardBase is the fixed amount allocated to the tower when the
    91  	// policy's blob type specifies a reward for the tower. This is taken
    92  	// before adding the proportional reward.
    93  	RewardBase uint32
    94  
    95  	// RewardRate is the fraction of the total balance of the revoked
    96  	// commitment that the watchtower is entitled to. This value is
    97  	// expressed in millionths of the total balance.
    98  	RewardRate uint32
    99  
   100  	// SweepFeeRate expresses the intended fee rate to be used when
   101  	// constructing the justice transaction. All sweep transactions created
   102  	// for this session must use this value during construction, and the
   103  	// signatures must implicitly commit to the resulting output values.
   104  	SweepFeeRate chainfee.AtomPerKByte
   105  }
   106  
   107  // Policy defines the negotiated parameters for a session between a client and
   108  // server. In addition to the TxPolicy that governs the shape of the justice
   109  // transaction, the Policy also includes features which only affect the
   110  // operation of the session.
   111  type Policy struct {
   112  	TxPolicy
   113  
   114  	// MaxUpdates is the maximum number of updates the watchtower will honor
   115  	// for this session.
   116  	MaxUpdates uint16
   117  }
   118  
   119  // String returns a human-readable description of the current policy.
   120  func (p Policy) String() string {
   121  	return fmt.Sprintf("(blob-type=%b max-updates=%d reward-rate=%d "+
   122  		"sweep-fee-rate=%d)", p.BlobType, p.MaxUpdates, p.RewardRate,
   123  		p.SweepFeeRate)
   124  }
   125  
   126  // IsAnchorChannel returns true if the session policy requires anchor channels.
   127  func (p Policy) IsAnchorChannel() bool {
   128  	return p.TxPolicy.BlobType.IsAnchorChannel()
   129  }
   130  
   131  // Validate ensures that the policy satisfies some minimal correctness
   132  // constraints.
   133  func (p Policy) Validate() error {
   134  	// RewardBase and RewardRate should not be set if the policy doesn't
   135  	// have a reward.
   136  	if !p.BlobType.Has(blob.FlagReward) &&
   137  		(p.RewardBase != 0 || p.RewardRate != 0) {
   138  
   139  		return ErrAltruistReward
   140  	}
   141  
   142  	// MaxUpdates must be positive.
   143  	if p.MaxUpdates == 0 {
   144  		return ErrNoMaxUpdates
   145  	}
   146  
   147  	// SweepFeeRate must be sane enough to get in the mempool during low
   148  	// congestion.
   149  	if p.SweepFeeRate < MinSweepFeeRate {
   150  		return ErrSweepFeeRateTooLow
   151  	}
   152  
   153  	return nil
   154  }
   155  
   156  // ComputeAltruistOutput computes the lone output value of a justice transaction
   157  // that pays no reward to the tower. The value is computed using the weight of
   158  // of the justice transaction and subtracting an amount that satisfies the
   159  // policy's fee rate.
   160  func (p *Policy) ComputeAltruistOutput(totalAmt dcrutil.Amount,
   161  	txSize int64) (dcrutil.Amount, error) {
   162  
   163  	txFee := p.SweepFeeRate.FeeForSize(txSize)
   164  	if txFee > totalAmt {
   165  		return 0, ErrFeeExceedsInputs
   166  	}
   167  
   168  	sweepAmt := totalAmt - txFee
   169  
   170  	// TODO(conner): replace w/ configurable dust limit
   171  	// Check that the created outputs won't be dusty. The sweep pkscript is
   172  	// currently a p2pkh, so we'll use that script's dust limit.
   173  	if sweepAmt < lnwallet.DustLimitForSize(input.P2PKHPkScriptSize) {
   174  		return 0, ErrCreatesDust
   175  	}
   176  
   177  	return sweepAmt, nil
   178  }
   179  
   180  // ComputeRewardOutputs splits the total funds in a breaching commitment
   181  // transaction between the victim and the tower, according to the sweep fee rate
   182  // and reward rate. The reward to he tower is subtracted first, before
   183  // splitting the remaining balance amongst the victim and fees.
   184  func (p *Policy) ComputeRewardOutputs(totalAmt dcrutil.Amount,
   185  	txSize int64) (dcrutil.Amount, dcrutil.Amount, error) {
   186  
   187  	txFee := p.SweepFeeRate.FeeForSize(txSize)
   188  	if txFee > totalAmt {
   189  		return 0, 0, ErrFeeExceedsInputs
   190  	}
   191  
   192  	// Apply the reward rate to the remaining total, specified in millionths
   193  	// of the available balance.
   194  	rewardAmt := ComputeRewardAmount(totalAmt, p.RewardBase, p.RewardRate)
   195  	if rewardAmt+txFee > totalAmt {
   196  		return 0, 0, ErrRewardExceedsInputs
   197  	}
   198  
   199  	// The sweep amount for the victim constitutes the remainder of the
   200  	// input value.
   201  	sweepAmt := totalAmt - rewardAmt - txFee
   202  
   203  	// TODO(conner): replace w/ configurable dust limit
   204  	// Check that the created outputs won't be dusty. The sweep pkscript is
   205  	// currently a p2pkh, so we'll use that script's dust limit.
   206  	if sweepAmt < lnwallet.DustLimitForSize(input.P2PKHPkScriptSize) {
   207  		return 0, 0, ErrCreatesDust
   208  	}
   209  
   210  	return sweepAmt, rewardAmt, nil
   211  }
   212  
   213  // ComputeRewardAmount computes the amount rewarded to the tower using the
   214  // proportional rate expressed in millionths, e.g. one million is equivalent to
   215  // one hundred percent of the total amount. The amount is rounded up to the
   216  // nearest whole satoshi.
   217  func ComputeRewardAmount(total dcrutil.Amount, base, rate uint32) dcrutil.Amount {
   218  	rewardBase := dcrutil.Amount(base)
   219  	rewardRate := dcrutil.Amount(rate)
   220  
   221  	// If the base reward exceeds the total, there is no more funds left
   222  	// from which to derive the proportional fee. We simply return the base,
   223  	// the caller should detect that this exceeds the total amount input.
   224  	if rewardBase > total {
   225  		return rewardBase
   226  	}
   227  
   228  	// Otherwise, subtract the base from the total and compute the
   229  	// proportional reward from the remaining total.
   230  	afterBase := total - rewardBase
   231  	proportional := (afterBase*rewardRate + RewardScale - 1) / RewardScale
   232  
   233  	return rewardBase + proportional
   234  }
   235  
   236  // ComputeJusticeTxOuts constructs the justice transaction outputs for the given
   237  // policy. If the policy specifies a reward for the tower, there will be two
   238  // outputs paying to the victim and the tower. Otherwise there will be a single
   239  // output sweeping funds back to the victim. The totalAmt should be the sum of
   240  // any inputs used in the transaction. The passed txWeight should include the
   241  // weight of the outputs for the justice transaction, which is dependent on
   242  // whether the justice transaction has a reward. The sweepPkScript should be the
   243  // pkScript of the victim to which funds will be recovered. The rewardPkScript
   244  // is the pkScript of the tower where its reward will be deposited, and will be
   245  // ignored if the blob type does not specify a reward.
   246  func (p *Policy) ComputeJusticeTxOuts(totalAmt dcrutil.Amount, txWeight int64,
   247  	sweepPkScript, rewardPkScript []byte) ([]*wire.TxOut, error) {
   248  
   249  	var outputs []*wire.TxOut
   250  
   251  	// If the policy specifies a reward for the tower, compute a split of
   252  	// the funds based on the policy's parameters. Otherwise, we will use an
   253  	// the altruist output computation and sweep as much of the funds back
   254  	// to the victim as possible.
   255  	if p.BlobType.Has(blob.FlagReward) {
   256  		// Using the total input amount and the transaction's weight,
   257  		// compute the sweep and reward amounts. This corresponds to the
   258  		// amount returned to the victim and the amount paid to the
   259  		// tower, respectively. To do so, the required transaction fee
   260  		// is subtracted from the total, and the remaining amount is
   261  		// divided according to the prenegotiated reward rate from the
   262  		// client's session info.
   263  		sweepAmt, rewardAmt, err := p.ComputeRewardOutputs(
   264  			totalAmt, txWeight,
   265  		)
   266  		if err != nil {
   267  			return nil, err
   268  		}
   269  
   270  		// Add the sweep and reward outputs to the list of txouts.
   271  		outputs = append(outputs, &wire.TxOut{
   272  			PkScript: sweepPkScript,
   273  			Value:    int64(sweepAmt),
   274  		})
   275  		outputs = append(outputs, &wire.TxOut{
   276  			PkScript: rewardPkScript,
   277  			Value:    int64(rewardAmt),
   278  		})
   279  	} else {
   280  		// Using the total input amount and the transaction's weight,
   281  		// compute the sweep amount, which corresponds to the amount
   282  		// returned to the victim. To do so, the required transaction
   283  		// fee is subtracted from the total input amount.
   284  		sweepAmt, err := p.ComputeAltruistOutput(
   285  			totalAmt, txWeight,
   286  		)
   287  		if err != nil {
   288  			return nil, err
   289  		}
   290  
   291  		// Add the sweep output to the list of txouts.
   292  		outputs = append(outputs, &wire.TxOut{
   293  			PkScript: sweepPkScript,
   294  			Value:    int64(sweepAmt),
   295  		})
   296  	}
   297  
   298  	return outputs, nil
   299  }