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 }