Merge pull request #3821 from halseth/pluggable-anchors-lnwallet
[anchor] pluggable anchor commitments
This commit is contained in:
37 files changed
+1848
-604
No files matched your search
+196
-180
@@ -503,7 +503,8 @@ type commitment struct {
|
||||
// evaluating all the add/remove/settle log entries before the listed
|
||||
// indexes.
|
||||
//
|
||||
// NOTE: This is the balance *after* subtracting any commitment fee.
|
||||
// NOTE: This is the balance *after* subtracting any commitment fee,
|
||||
// AND anchor output values.
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
theirBalance lnwire.MilliSatoshi
|
||||
|
||||
@@ -595,7 +596,7 @@ func locateOutputIndex(p *PaymentDescriptor, tx *wire.MsgTx, ourCommit bool,
|
||||
// we need to keep track of the indexes of each HTLC in order to properly write
|
||||
// the current state to disk, and also to locate the PaymentDescriptor
|
||||
// corresponding to HTLC outputs in the commitment transaction.
|
||||
func (c *commitment) populateHtlcIndexes() error {
|
||||
func (c *commitment) populateHtlcIndexes(chanType channeldb.ChannelType) error {
|
||||
// First, we'll set up some state to allow us to locate the output
|
||||
// index of the all the HTLC's within the commitment transaction. We
|
||||
// must keep this index so we can validate the HTLC signatures sent to
|
||||
@@ -607,8 +608,10 @@ func (c *commitment) populateHtlcIndexes() error {
|
||||
// populateIndex is a helper function that populates the necessary
|
||||
// indexes within the commitment view for a particular HTLC.
|
||||
populateIndex := func(htlc *PaymentDescriptor, incoming bool) error {
|
||||
isDust := htlcIsDust(incoming, c.isOurs, c.feePerKw,
|
||||
htlc.Amount.ToSatoshis(), c.dustLimit)
|
||||
isDust := htlcIsDust(
|
||||
chanType, incoming, c.isOurs, c.feePerKw,
|
||||
htlc.Amount.ToSatoshis(), c.dustLimit,
|
||||
)
|
||||
|
||||
var err error
|
||||
switch {
|
||||
@@ -773,6 +776,7 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
|
||||
ourWitnessScript, theirWitnessScript []byte
|
||||
pd PaymentDescriptor
|
||||
err error
|
||||
chanType = lc.channelState.ChanType
|
||||
)
|
||||
|
||||
// If the either outputs is dust from the local or remote node's
|
||||
@@ -780,22 +784,28 @@ func (lc *LightningChannel) diskHtlcToPayDesc(feeRate chainfee.SatPerKWeight,
|
||||
// generate them in order to locate the outputs within the commitment
|
||||
// transaction. As we'll mark dust with a special output index in the
|
||||
// on-disk state snapshot.
|
||||
isDustLocal := htlcIsDust(htlc.Incoming, true, feeRate,
|
||||
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit)
|
||||
isDustLocal := htlcIsDust(
|
||||
chanType, htlc.Incoming, true, feeRate,
|
||||
htlc.Amt.ToSatoshis(), lc.channelState.LocalChanCfg.DustLimit,
|
||||
)
|
||||
if !isDustLocal && localCommitKeys != nil {
|
||||
ourP2WSH, ourWitnessScript, err = genHtlcScript(
|
||||
htlc.Incoming, true, htlc.RefundTimeout, htlc.RHash,
|
||||
localCommitKeys)
|
||||
chanType, htlc.Incoming, true, htlc.RefundTimeout,
|
||||
htlc.RHash, localCommitKeys,
|
||||
)
|
||||
if err != nil {
|
||||
return pd, err
|
||||
}
|
||||
}
|
||||
isDustRemote := htlcIsDust(htlc.Incoming, false, feeRate,
|
||||
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit)
|
||||
isDustRemote := htlcIsDust(
|
||||
chanType, htlc.Incoming, false, feeRate,
|
||||
htlc.Amt.ToSatoshis(), lc.channelState.RemoteChanCfg.DustLimit,
|
||||
)
|
||||
if !isDustRemote && remoteCommitKeys != nil {
|
||||
theirP2WSH, theirWitnessScript, err = genHtlcScript(
|
||||
htlc.Incoming, false, htlc.RefundTimeout, htlc.RHash,
|
||||
remoteCommitKeys)
|
||||
chanType, htlc.Incoming, false, htlc.RefundTimeout,
|
||||
htlc.RHash, remoteCommitKeys,
|
||||
)
|
||||
if err != nil {
|
||||
return pd, err
|
||||
}
|
||||
@@ -926,7 +936,8 @@ func (lc *LightningChannel) diskCommitToMemCommit(isLocal bool,
|
||||
|
||||
// Finally, we'll re-populate the HTLC index for this state so we can
|
||||
// properly locate each HTLC within the commitment transaction.
|
||||
if err := commit.populateHtlcIndexes(); err != nil {
|
||||
err = commit.populateHtlcIndexes(lc.channelState.ChanType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -1406,11 +1417,14 @@ func (lc *LightningChannel) logUpdateToPayDesc(logUpdate *channeldb.LogUpdate,
|
||||
pd.OnionBlob = make([]byte, len(wireMsg.OnionBlob))
|
||||
copy(pd.OnionBlob[:], wireMsg.OnionBlob[:])
|
||||
|
||||
isDustRemote := htlcIsDust(false, false, feeRate,
|
||||
wireMsg.Amount.ToSatoshis(), remoteDustLimit)
|
||||
isDustRemote := htlcIsDust(
|
||||
lc.channelState.ChanType, false, false, feeRate,
|
||||
wireMsg.Amount.ToSatoshis(), remoteDustLimit,
|
||||
)
|
||||
if !isDustRemote {
|
||||
theirP2WSH, theirWitnessScript, err := genHtlcScript(
|
||||
false, false, wireMsg.Expiry, wireMsg.PaymentHash,
|
||||
lc.channelState.ChanType, false, false,
|
||||
wireMsg.Expiry, wireMsg.PaymentHash,
|
||||
remoteCommitKeys,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -2008,6 +2022,10 @@ type BreachRetribution struct {
|
||||
// party) within the breach transaction.
|
||||
LocalOutpoint wire.OutPoint
|
||||
|
||||
// LocalDelay is the CSV delay for the to_remote script on the breached
|
||||
// commitment.
|
||||
LocalDelay uint32
|
||||
|
||||
// RemoteOutputSignDesc is a SignDescriptor which is capable of
|
||||
// generating the signature required to claim the funds as described
|
||||
// within the revocation clause of the remote party's commitment
|
||||
@@ -2021,6 +2039,10 @@ type BreachRetribution struct {
|
||||
// party within the breach transaction.
|
||||
RemoteOutpoint wire.OutPoint
|
||||
|
||||
// RemoteDelay specifies the CSV delay applied to to-local scripts on
|
||||
// the breaching commitment transaction.
|
||||
RemoteDelay uint32
|
||||
|
||||
// HtlcRetributions is a slice of HTLC retributions for each output
|
||||
// active HTLC output within the breached commitment transaction.
|
||||
HtlcRetributions []HtlcRetribution
|
||||
@@ -2029,10 +2051,6 @@ type BreachRetribution struct {
|
||||
// breaching commitment transaction. This allows downstream clients to
|
||||
// have access to the public keys used in the scripts.
|
||||
KeyRing *CommitmentKeyRing
|
||||
|
||||
// RemoteDelay specifies the CSV delay applied to to-local scripts on
|
||||
// the breaching commitment transaction.
|
||||
RemoteDelay uint32
|
||||
}
|
||||
|
||||
// NewBreachRetribution creates a new fully populated BreachRetribution for the
|
||||
@@ -2085,9 +2103,8 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
|
||||
// Since it is the remote breach we are reconstructing, the output going
|
||||
// to us will be a to-remote script with our local params.
|
||||
ourDelay := uint32(chanState.LocalChanCfg.CsvDelay)
|
||||
ourScript, err := CommitScriptToRemote(
|
||||
chanState.ChanType, ourDelay, keyRing.ToRemoteKey,
|
||||
ourScript, ourDelay, err := CommitScriptToRemote(
|
||||
chanState.ChanType, keyRing.ToRemoteKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -2157,15 +2174,10 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
// remote commitment transaction.
|
||||
htlcRetributions := make([]HtlcRetribution, 0, len(revokedSnapshot.Htlcs))
|
||||
for _, htlc := range revokedSnapshot.Htlcs {
|
||||
var (
|
||||
htlcWitnessScript []byte
|
||||
err error
|
||||
)
|
||||
|
||||
// If the HTLC is dust, then we'll skip it as it doesn't have
|
||||
// an output on the commitment transaction.
|
||||
if htlcIsDust(
|
||||
htlc.Incoming, false,
|
||||
chanState.ChanType, htlc.Incoming, false,
|
||||
chainfee.SatPerKWeight(revokedSnapshot.FeePerKw),
|
||||
htlc.Amt.ToSatoshis(), chanState.RemoteChanCfg.DustLimit,
|
||||
) {
|
||||
@@ -2185,31 +2197,13 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
|
||||
// If this is an incoming HTLC, then this means that they were
|
||||
// the sender of the HTLC (relative to us). So we'll
|
||||
// re-generate the sender HTLC script.
|
||||
if htlc.Incoming {
|
||||
htlcWitnessScript, err = input.SenderHTLCScript(
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
} else {
|
||||
// Otherwise, is this was an outgoing HTLC that we
|
||||
// sent, then from the PoV of the remote commitment
|
||||
// state, they're the receiver of this HTLC.
|
||||
htlcWitnessScript, err = input.ReceiverHTLCScript(
|
||||
htlc.RefundTimeout, keyRing.LocalHtlcKey,
|
||||
keyRing.RemoteHtlcKey, keyRing.RevocationKey,
|
||||
htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
htlcPkScript, err := input.WitnessScriptHash(htlcWitnessScript)
|
||||
// re-generate the sender HTLC script. Otherwise, is this was
|
||||
// an outgoing HTLC that we sent, then from the PoV of the
|
||||
// remote commitment state, they're the receiver of this HTLC.
|
||||
htlcPkScript, htlcWitnessScript, err := genHtlcScript(
|
||||
chanState.ChanType, htlc.Incoming, false,
|
||||
htlc.RefundTimeout, htlc.RHash, keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2245,33 +2239,23 @@ func NewBreachRetribution(chanState *channeldb.OpenChannel, stateNum uint64,
|
||||
PendingHTLCs: revokedSnapshot.Htlcs,
|
||||
LocalOutpoint: ourOutpoint,
|
||||
LocalOutputSignDesc: ourSignDesc,
|
||||
LocalDelay: ourDelay,
|
||||
RemoteOutpoint: theirOutpoint,
|
||||
RemoteOutputSignDesc: theirSignDesc,
|
||||
RemoteDelay: theirDelay,
|
||||
HtlcRetributions: htlcRetributions,
|
||||
KeyRing: keyRing,
|
||||
RemoteDelay: theirDelay,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// htlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
|
||||
// transaction based on the current fee rate.
|
||||
func htlcTimeoutFee(feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
|
||||
}
|
||||
|
||||
// htlcSuccessFee returns the fee in satoshis required for an HTLC success
|
||||
// transaction based on the current fee rate.
|
||||
func htlcSuccessFee(feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
|
||||
}
|
||||
|
||||
// htlcIsDust determines if an HTLC output is dust or not depending on two
|
||||
// bits: if the HTLC is incoming and if the HTLC will be placed on our
|
||||
// commitment transaction, or theirs. These two pieces of information are
|
||||
// require as we currently used second-level HTLC transactions as off-chain
|
||||
// covenants. Depending on the two bits, we'll either be using a timeout or
|
||||
// success transaction which have different weights.
|
||||
func htlcIsDust(incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
|
||||
func htlcIsDust(chanType channeldb.ChannelType,
|
||||
incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
|
||||
htlcAmt, dustLimit btcutil.Amount) bool {
|
||||
|
||||
// First we'll determine the fee required for this HTLC based on if this is
|
||||
@@ -2283,25 +2267,25 @@ func htlcIsDust(incoming, ourCommit bool, feePerKw chainfee.SatPerKWeight,
|
||||
// If this is an incoming HTLC on our commitment transaction, then the
|
||||
// second-level transaction will be a success transaction.
|
||||
case incoming && ourCommit:
|
||||
htlcFee = htlcSuccessFee(feePerKw)
|
||||
htlcFee = HtlcSuccessFee(chanType, feePerKw)
|
||||
|
||||
// If this is an incoming HTLC on their commitment transaction, then
|
||||
// we'll be using a second-level timeout transaction as they've added
|
||||
// this HTLC.
|
||||
case incoming && !ourCommit:
|
||||
htlcFee = htlcTimeoutFee(feePerKw)
|
||||
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
|
||||
|
||||
// If this is an outgoing HTLC on our commitment transaction, then
|
||||
// we'll be using a timeout transaction as we're the sender of the
|
||||
// HTLC.
|
||||
case !incoming && ourCommit:
|
||||
htlcFee = htlcTimeoutFee(feePerKw)
|
||||
htlcFee = HtlcTimeoutFee(chanType, feePerKw)
|
||||
|
||||
// If this is an outgoing HTLC on their commitment transaction, then
|
||||
// we'll be using an HTLC success transaction as they're the receiver
|
||||
// of this HTLC.
|
||||
case !incoming && !ourCommit:
|
||||
htlcFee = htlcSuccessFee(feePerKw)
|
||||
htlcFee = HtlcSuccessFee(chanType, feePerKw)
|
||||
}
|
||||
|
||||
return (htlcAmt - htlcFee) < dustLimit
|
||||
@@ -2391,6 +2375,29 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We'll assert that there hasn't been a mistake during fee calculation
|
||||
// leading to a fee too low.
|
||||
var totalOut btcutil.Amount
|
||||
for _, txOut := range commitTx.txn.TxOut {
|
||||
totalOut += btcutil.Amount(txOut.Value)
|
||||
}
|
||||
fee := lc.channelState.Capacity - totalOut
|
||||
|
||||
// Since the transaction is not signed yet, we use the witness weight
|
||||
// used for weight calculation.
|
||||
uTx := btcutil.NewTx(commitTx.txn)
|
||||
weight := blockchain.GetTransactionWeight(uTx) +
|
||||
input.WitnessCommitmentTxWeight
|
||||
|
||||
effFeeRate := chainfee.SatPerKWeight(fee) * 1000 /
|
||||
chainfee.SatPerKWeight(weight)
|
||||
if effFeeRate < chainfee.FeePerKwFloor {
|
||||
return nil, fmt.Errorf("height=%v, for ChannelPoint(%v) "+
|
||||
"attempts to create commitment wigh feerate %v: %v",
|
||||
nextHeight, lc.channelState.FundingOutpoint,
|
||||
effFeeRate, spew.Sdump(commitTx))
|
||||
}
|
||||
|
||||
// With the commitment view created, store the resulting balances and
|
||||
// transaction with the other parameters for this height.
|
||||
c := &commitment{
|
||||
@@ -2422,7 +2429,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
||||
|
||||
// Finally, we'll populate all the HTLC indexes so we can track the
|
||||
// locations of each HTLC in the commitment state.
|
||||
if err := c.populateHtlcIndexes(); err != nil {
|
||||
if err := c.populateHtlcIndexes(lc.channelState.ChanType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2737,12 +2744,14 @@ func processFeeUpdate(feeUpdate *PaymentDescriptor, nextHeight uint64,
|
||||
// signature can be submitted to the sigPool to generate all the signatures
|
||||
// asynchronously and in parallel.
|
||||
func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
chanType channeldb.ChannelType,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||
remoteCommitView *commitment) ([]SignJob, chan struct{}, error) {
|
||||
|
||||
txHash := remoteCommitView.txn.TxHash()
|
||||
dustLimit := remoteChanCfg.DustLimit
|
||||
feePerKw := remoteCommitView.feePerKw
|
||||
sigHashType := HtlcSigHashType(chanType)
|
||||
|
||||
// With the keys generated, we'll make a slice with enough capacity to
|
||||
// hold potentially all the HTLCs. The actual slice may be a bit
|
||||
@@ -2758,8 +2767,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// dust output after taking into account second-level HTLC fees, then a
|
||||
// sigJob will be generated and appended to the current batch.
|
||||
for _, htlc := range remoteCommitView.incomingHTLCs {
|
||||
if htlcIsDust(true, false, feePerKw, htlc.Amount.ToSatoshis(),
|
||||
dustLimit) {
|
||||
if htlcIsDust(
|
||||
chanType, true, false, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -2774,7 +2785,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// HTLC timeout transaction for them. The output of the timeout
|
||||
// transaction needs to account for fees, so we'll compute the
|
||||
// required fee and output now.
|
||||
htlcFee := htlcTimeoutFee(feePerKw)
|
||||
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
|
||||
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
|
||||
|
||||
// With the fee calculate, we can properly create the HTLC
|
||||
@@ -2784,7 +2795,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
Index: uint32(htlc.remoteOutputIndex),
|
||||
}
|
||||
sigJob.Tx, err = createHtlcTimeoutTx(
|
||||
op, outputAmt, htlc.Timeout,
|
||||
chanType, op, outputAmt, htlc.Timeout,
|
||||
uint32(remoteChanCfg.CsvDelay),
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
@@ -2802,7 +2813,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(htlc.Amount.ToSatoshis()),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
HashType: sigHashType,
|
||||
SigHashes: txscript.NewTxSigHashes(sigJob.Tx),
|
||||
InputIndex: 0,
|
||||
}
|
||||
@@ -2811,8 +2822,10 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
sigBatch = append(sigBatch, sigJob)
|
||||
}
|
||||
for _, htlc := range remoteCommitView.outgoingHTLCs {
|
||||
if htlcIsDust(false, false, feePerKw, htlc.Amount.ToSatoshis(),
|
||||
dustLimit) {
|
||||
if htlcIsDust(
|
||||
chanType, false, false, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -2825,7 +2838,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
// HTLC success transaction for them. The output of the timeout
|
||||
// transaction needs to account for fees, so we'll compute the
|
||||
// required fee and output now.
|
||||
htlcFee := htlcSuccessFee(feePerKw)
|
||||
htlcFee := HtlcSuccessFee(chanType, feePerKw)
|
||||
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
|
||||
|
||||
// With the proper output amount calculated, we can now
|
||||
@@ -2836,7 +2849,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
Index: uint32(htlc.remoteOutputIndex),
|
||||
}
|
||||
sigJob.Tx, err = createHtlcSuccessTx(
|
||||
op, outputAmt, uint32(remoteChanCfg.CsvDelay),
|
||||
chanType, op, outputAmt, uint32(remoteChanCfg.CsvDelay),
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -2853,7 +2866,7 @@ func genRemoteHtlcSigJobs(keyRing *CommitmentKeyRing,
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(htlc.Amount.ToSatoshis()),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
HashType: sigHashType,
|
||||
SigHashes: txscript.NewTxSigHashes(sigJob.Tx),
|
||||
InputIndex: 0,
|
||||
}
|
||||
@@ -3341,7 +3354,8 @@ func (lc *LightningChannel) SignNextCommitment() (lnwire.Sig, []lnwire.Sig, []ch
|
||||
// need to generate signatures of each of them for the remote party's
|
||||
// commitment state. We do so in two phases: first we generate and
|
||||
// submit the set of signature jobs to the worker pool.
|
||||
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(keyRing,
|
||||
sigBatch, cancelChan, err := genRemoteHtlcSigJobs(
|
||||
keyRing, lc.channelState.ChanType,
|
||||
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
|
||||
newCommitView,
|
||||
)
|
||||
@@ -3773,23 +3787,28 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||
// weight, needed to calculate the transaction fee.
|
||||
var totalHtlcWeight int64
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(remoteChain, !remoteChain, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
if htlcIsDust(
|
||||
lc.channelState.ChanType, remoteChain, !remoteChain,
|
||||
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalHtlcWeight += input.HTLCWeight
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(!remoteChain, !remoteChain, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
if htlcIsDust(
|
||||
lc.channelState.ChanType, !remoteChain, !remoteChain,
|
||||
feePerKw, htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
totalHtlcWeight += input.HTLCWeight
|
||||
}
|
||||
|
||||
totalCommitWeight := input.CommitWeight + totalHtlcWeight
|
||||
totalCommitWeight := CommitWeight(lc.channelState.ChanType) +
|
||||
totalHtlcWeight
|
||||
return ourBalance, theirBalance, totalCommitWeight, filteredHTLCView, nil
|
||||
}
|
||||
|
||||
@@ -3799,10 +3818,12 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||
// directly into the pool of workers.
|
||||
func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
||||
keyRing *CommitmentKeyRing, htlcSigs []lnwire.Sig,
|
||||
chanType channeldb.ChannelType,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig) ([]VerifyJob, error) {
|
||||
|
||||
txHash := localCommitmentView.txn.TxHash()
|
||||
feePerKw := localCommitmentView.feePerKw
|
||||
sigHashType := HtlcSigHashType(chanType)
|
||||
|
||||
// With the required state generated, we'll create a slice with large
|
||||
// enough capacity to hold verification jobs for all HTLC's in this
|
||||
@@ -3842,12 +3863,14 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
||||
Index: uint32(htlc.localOutputIndex),
|
||||
}
|
||||
|
||||
htlcFee := htlcSuccessFee(feePerKw)
|
||||
htlcFee := HtlcSuccessFee(chanType, feePerKw)
|
||||
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
|
||||
|
||||
successTx, err := createHtlcSuccessTx(op,
|
||||
outputAmt, uint32(localChanCfg.CsvDelay),
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey)
|
||||
successTx, err := createHtlcSuccessTx(
|
||||
chanType, op, outputAmt,
|
||||
uint32(localChanCfg.CsvDelay),
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -3855,7 +3878,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
||||
hashCache := txscript.NewTxSigHashes(successTx)
|
||||
sigHash, err := txscript.CalcWitnessSigHash(
|
||||
htlc.ourWitnessScript, hashCache,
|
||||
txscript.SigHashAll, successTx, 0,
|
||||
sigHashType, successTx, 0,
|
||||
int64(htlc.Amount.ToSatoshis()),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -3894,11 +3917,11 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
||||
Index: uint32(htlc.localOutputIndex),
|
||||
}
|
||||
|
||||
htlcFee := htlcTimeoutFee(feePerKw)
|
||||
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
|
||||
outputAmt := htlc.Amount.ToSatoshis() - htlcFee
|
||||
|
||||
timeoutTx, err := createHtlcTimeoutTx(op,
|
||||
outputAmt, htlc.Timeout,
|
||||
timeoutTx, err := createHtlcTimeoutTx(
|
||||
chanType, op, outputAmt, htlc.Timeout,
|
||||
uint32(localChanCfg.CsvDelay),
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
@@ -3909,7 +3932,7 @@ func genHtlcSigValidationJobs(localCommitmentView *commitment,
|
||||
hashCache := txscript.NewTxSigHashes(timeoutTx)
|
||||
sigHash, err := txscript.CalcWitnessSigHash(
|
||||
htlc.ourWitnessScript, hashCache,
|
||||
txscript.SigHashAll, timeoutTx, 0,
|
||||
sigHashType, timeoutTx, 0,
|
||||
int64(htlc.Amount.ToSatoshis()),
|
||||
)
|
||||
if err != nil {
|
||||
@@ -4115,7 +4138,8 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSig lnwire.Sig,
|
||||
// generated, we'll submit these jobs to the worker pool.
|
||||
verifyJobs, err := genHtlcSigValidationJobs(
|
||||
localCommitmentView, keyRing, htlcSigs,
|
||||
&lc.channelState.LocalChanCfg, &lc.channelState.RemoteChanCfg,
|
||||
lc.channelState.ChanType, &lc.channelState.LocalChanCfg,
|
||||
&lc.channelState.RemoteChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -5040,8 +5064,8 @@ func (lc *LightningChannel) getSignedCommitTx() (*wire.MsgTx, error) {
|
||||
}
|
||||
|
||||
// CommitOutputResolution carries the necessary information required to allow
|
||||
// us to sweep our direct commitment output in the case that either party goes
|
||||
// to chain.
|
||||
// us to sweep our commitment output in the case that either party goes to
|
||||
// chain.
|
||||
type CommitOutputResolution struct {
|
||||
// SelfOutPoint is the full outpoint that points to out pay-to-self
|
||||
// output within the closing commitment transaction.
|
||||
@@ -5053,8 +5077,7 @@ type CommitOutputResolution struct {
|
||||
|
||||
// MaturityDelay is the relative time-lock, in blocks for all outputs
|
||||
// that pay to the local party within the broadcast commitment
|
||||
// transaction. This value will be non-zero iff, this output was on our
|
||||
// commitment transaction.
|
||||
// transaction.
|
||||
MaturityDelay uint32
|
||||
}
|
||||
|
||||
@@ -5123,6 +5146,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
chainfee.SatPerKWeight(remoteCommit.FeePerKw), false, signer,
|
||||
remoteCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
||||
&chanState.RemoteChanCfg, *commitSpend.SpenderTxHash,
|
||||
chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create htlc "+
|
||||
@@ -5134,9 +5158,8 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
// Before we can generate the proper sign descriptor, we'll need to
|
||||
// locate the output index of our non-delayed output on the commitment
|
||||
// transaction.
|
||||
localDelay := uint32(chanState.LocalChanCfg.CsvDelay)
|
||||
selfScript, err := CommitScriptToRemote(
|
||||
chanState.ChanType, localDelay, keyRing.ToRemoteKey,
|
||||
selfScript, maturityDelay, err := CommitScriptToRemote(
|
||||
chanState.ChanType, keyRing.ToRemoteKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to create self commit "+
|
||||
@@ -5177,7 +5200,7 @@ func NewUnilateralCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
MaturityDelay: 0,
|
||||
MaturityDelay: maturityDelay,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5241,7 +5264,8 @@ type IncomingHtlcResolution struct {
|
||||
// pass after the SignedSuccessTx is confirmed in the chain before the
|
||||
// output can be swept.
|
||||
//
|
||||
// NOTE: If SignedSuccessTx is nil, then this field isn't needed.
|
||||
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
|
||||
// delay needed to spend from the commitment transaction.
|
||||
CsvDelay uint32
|
||||
|
||||
// ClaimOutpoint is the final outpoint that needs to be spent in order
|
||||
@@ -5281,7 +5305,8 @@ type OutgoingHtlcResolution struct {
|
||||
// pass after the SignedTimeoutTx is confirmed in the chain before the
|
||||
// output can be swept.
|
||||
//
|
||||
// NOTE: If SignedTimeoutTx is nil, then this field isn't needed.
|
||||
// NOTE: If SignedTimeoutTx is nil, then this field denotes the CSV
|
||||
// delay needed to spend from the commitment transaction.
|
||||
CsvDelay uint32
|
||||
|
||||
// ClaimOutpoint is the final outpoint that needs to be spent in order
|
||||
@@ -5318,31 +5343,27 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash,
|
||||
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||
feePerKw chainfee.SatPerKWeight, csvDelay uint32,
|
||||
localCommit bool) (*OutgoingHtlcResolution, error) {
|
||||
localCommit bool, chanType channeldb.ChannelType) (*OutgoingHtlcResolution, error) {
|
||||
|
||||
op := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: uint32(htlc.OutputIndex),
|
||||
}
|
||||
|
||||
// First, we'll re-generate the script used to send the HTLC to
|
||||
// the remote party within their commitment transaction.
|
||||
htlcScriptHash, htlcScript, err := genHtlcScript(
|
||||
chanType, false, localCommit, htlc.RefundTimeout, htlc.RHash,
|
||||
keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we're spending this HTLC output from the remote node's
|
||||
// commitment, then we won't need to go to the second level as our
|
||||
// outputs don't have a CSV delay.
|
||||
if !localCommit {
|
||||
// First, we'll re-generate the script used to send the HTLC to
|
||||
// the remote party within their commitment transaction.
|
||||
htlcReceiverScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout,
|
||||
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RevocationKey, htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htlcScriptHash, err := input.WitnessScriptHash(htlcReceiverScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the script generated, we can completely populated the
|
||||
// SignDescriptor needed to sweep the output.
|
||||
return &OutgoingHtlcResolution{
|
||||
@@ -5351,13 +5372,14 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
SweepSignDesc: input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.HtlcBasePoint,
|
||||
SingleTweak: keyRing.LocalHtlcKeyTweak,
|
||||
WitnessScript: htlcReceiverScript,
|
||||
WitnessScript: htlcScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcScriptHash,
|
||||
Value: int64(htlc.Amt.ToSatoshis()),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
CsvDelay: HtlcSecondLevelInputSequence(chanType),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -5367,14 +5389,14 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
// In order to properly reconstruct the HTLC transaction, we'll need to
|
||||
// re-calculate the fee required at this state, so we can add the
|
||||
// correct output value amount to the transaction.
|
||||
htlcFee := htlcTimeoutFee(feePerKw)
|
||||
htlcFee := HtlcTimeoutFee(chanType, feePerKw)
|
||||
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
|
||||
|
||||
// With the fee calculated, re-construct the second level timeout
|
||||
// transaction.
|
||||
timeoutTx, err := createHtlcTimeoutTx(
|
||||
op, secondLevelOutputAmt, htlc.RefundTimeout, csvDelay,
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
chanType, op, secondLevelOutputAmt, htlc.RefundTimeout,
|
||||
csvDelay, keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -5383,15 +5405,10 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
// With the transaction created, we can generate a sign descriptor
|
||||
// that's capable of generating the signature required to spend the
|
||||
// HTLC output using the timeout transaction.
|
||||
htlcCreationScript, err := input.SenderHTLCScript(keyRing.LocalHtlcKey,
|
||||
keyRing.RemoteHtlcKey, keyRing.RevocationKey, htlc.RHash[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
timeoutSignDesc := input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.HtlcBasePoint,
|
||||
SingleTweak: keyRing.LocalHtlcKeyTweak,
|
||||
WitnessScript: htlcCreationScript,
|
||||
WitnessScript: htlcScript,
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(htlc.Amt.ToSatoshis()),
|
||||
},
|
||||
@@ -5402,8 +5419,9 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
|
||||
// With the sign desc created, we can now construct the full witness
|
||||
// for the timeout transaction, and populate it as well.
|
||||
sigHashType := HtlcSigHashType(chanType)
|
||||
timeoutWitness, err := input.SenderHtlcSpendTimeout(
|
||||
htlc.Signature, signer, &timeoutSignDesc, timeoutTx,
|
||||
htlc.Signature, sigHashType, signer, &timeoutSignDesc, timeoutTx,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -5419,7 +5437,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
|
||||
htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5440,7 +5458,7 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
SingleTweak: localDelayTweak,
|
||||
WitnessScript: htlcSweepScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcScriptHash,
|
||||
PkScript: htlcSweepScriptHash,
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
@@ -5455,48 +5473,45 @@ func newOutgoingHtlcResolution(signer input.Signer,
|
||||
// they can just sweep the output immediately with knowledge of the pre-image.
|
||||
//
|
||||
// TODO(roasbeef) consolidate code with above func
|
||||
func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.ChannelConfig,
|
||||
commitHash chainhash.Hash, htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||
feePerKw chainfee.SatPerKWeight, csvDelay uint32,
|
||||
localCommit bool) (*IncomingHtlcResolution, error) {
|
||||
func newIncomingHtlcResolution(signer input.Signer,
|
||||
localChanCfg *channeldb.ChannelConfig, commitHash chainhash.Hash,
|
||||
htlc *channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||
feePerKw chainfee.SatPerKWeight, csvDelay uint32, localCommit bool,
|
||||
chanType channeldb.ChannelType) (*IncomingHtlcResolution, error) {
|
||||
|
||||
op := wire.OutPoint{
|
||||
Hash: commitHash,
|
||||
Index: uint32(htlc.OutputIndex),
|
||||
}
|
||||
|
||||
// First, we'll re-generate the script the remote party used to
|
||||
// send the HTLC to us in their commitment transaction.
|
||||
htlcScriptHash, htlcScript, err := genHtlcScript(
|
||||
chanType, true, localCommit, htlc.RefundTimeout, htlc.RHash,
|
||||
keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If we're spending this output from the remote node's commitment,
|
||||
// then we can skip the second layer and spend the output directly.
|
||||
if !localCommit {
|
||||
// First, we'll re-generate the script the remote party used to
|
||||
// send the HTLC to us in their commitment transaction.
|
||||
htlcSenderScript, err := input.SenderHTLCScript(
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htlcScriptHash, err := input.WitnessScriptHash(htlcSenderScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// With the script generated, we can completely populated the
|
||||
// SignDescriptor needed to sweep the output.
|
||||
return &IncomingHtlcResolution{
|
||||
ClaimOutpoint: op,
|
||||
CsvDelay: csvDelay,
|
||||
SweepSignDesc: input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.HtlcBasePoint,
|
||||
SingleTweak: keyRing.LocalHtlcKeyTweak,
|
||||
WitnessScript: htlcSenderScript,
|
||||
WitnessScript: htlcScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcScriptHash,
|
||||
Value: int64(htlc.Amt.ToSatoshis()),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
},
|
||||
CsvDelay: HtlcSecondLevelInputSequence(chanType),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -5504,10 +5519,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
||||
|
||||
// First, we'll reconstruct the original HTLC success transaction,
|
||||
// taking into account the fee rate used.
|
||||
htlcFee := htlcSuccessFee(feePerKw)
|
||||
htlcFee := HtlcSuccessFee(chanType, feePerKw)
|
||||
secondLevelOutputAmt := htlc.Amt.ToSatoshis() - htlcFee
|
||||
successTx, err := createHtlcSuccessTx(
|
||||
op, secondLevelOutputAmt, csvDelay,
|
||||
chanType, op, secondLevelOutputAmt, csvDelay,
|
||||
keyRing.RevocationKey, keyRing.ToLocalKey,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -5516,17 +5531,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
||||
|
||||
// Once we've created the second-level transaction, we'll generate the
|
||||
// SignDesc needed spend the HTLC output using the success transaction.
|
||||
htlcCreationScript, err := input.ReceiverHTLCScript(htlc.RefundTimeout,
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, htlc.RHash[:],
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
successSignDesc := input.SignDescriptor{
|
||||
KeyDesc: localChanCfg.HtlcBasePoint,
|
||||
SingleTweak: keyRing.LocalHtlcKeyTweak,
|
||||
WitnessScript: htlcCreationScript,
|
||||
WitnessScript: htlcScript,
|
||||
Output: &wire.TxOut{
|
||||
Value: int64(htlc.Amt.ToSatoshis()),
|
||||
},
|
||||
@@ -5539,8 +5547,10 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
||||
// the success transaction. Don't specify the preimage yet. The preimage
|
||||
// will be supplied by the contract resolver, either directly or when it
|
||||
// becomes known.
|
||||
sigHashType := HtlcSigHashType(chanType)
|
||||
successWitness, err := input.ReceiverHtlcSpendRedeem(
|
||||
htlc.Signature, nil, signer, &successSignDesc, successTx,
|
||||
htlc.Signature, sigHashType, nil, signer, &successSignDesc,
|
||||
successTx,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -5556,7 +5566,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
htlcScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
|
||||
htlcSweepScriptHash, err := input.WitnessScriptHash(htlcSweepScript)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -5576,7 +5586,7 @@ func newIncomingHtlcResolution(signer input.Signer, localChanCfg *channeldb.Chan
|
||||
SingleTweak: localDelayTweak,
|
||||
WitnessScript: htlcSweepScript,
|
||||
Output: &wire.TxOut{
|
||||
PkScript: htlcScriptHash,
|
||||
PkScript: htlcSweepScriptHash,
|
||||
Value: int64(secondLevelOutputAmt),
|
||||
},
|
||||
HashType: txscript.SigHashAll,
|
||||
@@ -5614,7 +5624,8 @@ func (r *OutgoingHtlcResolution) HtlcPoint() wire.OutPoint {
|
||||
func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
|
||||
signer input.Signer, htlcs []channeldb.HTLC, keyRing *CommitmentKeyRing,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||
commitHash chainhash.Hash) (*HtlcResolutions, error) {
|
||||
commitHash chainhash.Hash, chanType channeldb.ChannelType) (
|
||||
*HtlcResolutions, error) {
|
||||
|
||||
// TODO(roasbeef): don't need to swap csv delay?
|
||||
dustLimit := remoteChanCfg.DustLimit
|
||||
@@ -5627,11 +5638,15 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
|
||||
incomingResolutions := make([]IncomingHtlcResolution, 0, len(htlcs))
|
||||
outgoingResolutions := make([]OutgoingHtlcResolution, 0, len(htlcs))
|
||||
for _, htlc := range htlcs {
|
||||
htlc := htlc
|
||||
|
||||
// We'll skip any HTLC's which were dust on the commitment
|
||||
// transaction, as these don't have a corresponding output
|
||||
// within the commitment transaction.
|
||||
if htlcIsDust(htlc.Incoming, ourCommit, feePerKw,
|
||||
htlc.Amt.ToSatoshis(), dustLimit) {
|
||||
if htlcIsDust(
|
||||
chanType, htlc.Incoming, ourCommit, feePerKw,
|
||||
htlc.Amt.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -5641,8 +5656,9 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
|
||||
// Otherwise, we'll create an incoming HTLC resolution
|
||||
// as we can satisfy the contract.
|
||||
ihr, err := newIncomingHtlcResolution(
|
||||
signer, localChanCfg, commitHash, &htlc, keyRing,
|
||||
feePerKw, uint32(csvDelay), ourCommit,
|
||||
signer, localChanCfg, commitHash, &htlc,
|
||||
keyRing, feePerKw, uint32(csvDelay), ourCommit,
|
||||
chanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -5654,7 +5670,7 @@ func extractHtlcResolutions(feePerKw chainfee.SatPerKWeight, ourCommit bool,
|
||||
|
||||
ohr, err := newOutgoingHtlcResolution(
|
||||
signer, localChanCfg, commitHash, &htlc, keyRing,
|
||||
feePerKw, uint32(csvDelay), ourCommit,
|
||||
feePerKw, uint32(csvDelay), ourCommit, chanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -5833,7 +5849,7 @@ func NewLocalForceCloseSummary(chanState *channeldb.OpenChannel, signer input.Si
|
||||
htlcResolutions, err := extractHtlcResolutions(
|
||||
chainfee.SatPerKWeight(localCommit.FeePerKw), true, signer,
|
||||
localCommit.Htlcs, keyRing, &chanState.LocalChanCfg,
|
||||
&chanState.RemoteChanCfg, txHash,
|
||||
&chanState.RemoteChanCfg, txHash, chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -6133,7 +6149,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView,
|
||||
// For an extra HTLC fee to be paid on our commitment, the HTLC must be
|
||||
// large enough to make a non-dust HTLC timeout transaction.
|
||||
htlcFee := lnwire.NewMSatFromSatoshis(
|
||||
htlcTimeoutFee(feePerKw),
|
||||
HtlcTimeoutFee(lc.channelState.ChanType, feePerKw),
|
||||
)
|
||||
|
||||
// If we are looking at the remote commitment, we must use the remote
|
||||
@@ -6143,7 +6159,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView,
|
||||
lc.channelState.RemoteChanCfg.DustLimit,
|
||||
)
|
||||
htlcFee = lnwire.NewMSatFromSatoshis(
|
||||
htlcSuccessFee(feePerKw),
|
||||
HtlcSuccessFee(lc.channelState.ChanType, feePerKw),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6351,7 +6367,7 @@ func CreateCooperativeCloseTx(fundingTxIn wire.TxIn,
|
||||
// CalcFee returns the commitment fee to use for the given
|
||||
// fee rate (fee-per-kw).
|
||||
func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amount {
|
||||
return feeRate.FeeForWeight(input.CommitWeight)
|
||||
return feeRate.FeeForWeight(CommitWeight(lc.channelState.ChanType))
|
||||
}
|
||||
|
||||
// MaxFeeRate returns the maximum fee rate given an allocation of the channel
|
||||
|
||||
@@ -1011,7 +1011,8 @@ func TestHTLCDustLimit(t *testing.T) {
|
||||
|
||||
// The amount of the HTLC should be above Alice's dust limit and below
|
||||
// Bob's dust limit.
|
||||
htlcSat := (btcutil.Amount(500) + htlcTimeoutFee(
|
||||
htlcSat := (btcutil.Amount(500) + HtlcTimeoutFee(
|
||||
aliceChannel.channelState.ChanType,
|
||||
chainfee.SatPerKWeight(
|
||||
aliceChannel.channelState.LocalCommitment.FeePerKw,
|
||||
),
|
||||
@@ -1119,8 +1120,12 @@ func TestHTLCSigNumber(t *testing.T) {
|
||||
t.Fatalf("unable to get fee: %v", err)
|
||||
}
|
||||
|
||||
belowDust := btcutil.Amount(500) + htlcTimeoutFee(feePerKw)
|
||||
aboveDust := btcutil.Amount(1400) + htlcSuccessFee(feePerKw)
|
||||
belowDust := btcutil.Amount(500) + HtlcTimeoutFee(
|
||||
channeldb.SingleFunderTweaklessBit, feePerKw,
|
||||
)
|
||||
aboveDust := btcutil.Amount(1400) + HtlcSuccessFee(
|
||||
channeldb.SingleFunderTweaklessBit, feePerKw,
|
||||
)
|
||||
|
||||
// ===================================================================
|
||||
// Test that Bob will reject a commitment if Alice doesn't send enough
|
||||
@@ -1278,7 +1283,8 @@ func TestChannelBalanceDustLimit(t *testing.T) {
|
||||
defaultFee := calcStaticFee(1)
|
||||
aliceBalance := aliceChannel.channelState.LocalCommitment.LocalBalance.ToSatoshis()
|
||||
htlcSat := aliceBalance - defaultFee
|
||||
htlcSat += htlcSuccessFee(
|
||||
htlcSat += HtlcSuccessFee(
|
||||
aliceChannel.channelState.ChanType,
|
||||
chainfee.SatPerKWeight(
|
||||
aliceChannel.channelState.LocalCommitment.FeePerKw,
|
||||
),
|
||||
@@ -4759,10 +4765,10 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) {
|
||||
aliceChannel.channelState.LocalCommitment.CommitFee,
|
||||
)
|
||||
htlcTimeoutFee := lnwire.NewMSatFromSatoshis(
|
||||
htlcTimeoutFee(feeRate),
|
||||
HtlcTimeoutFee(aliceChannel.channelState.ChanType, feeRate),
|
||||
)
|
||||
htlcSuccessFee := lnwire.NewMSatFromSatoshis(
|
||||
htlcSuccessFee(feeRate),
|
||||
HtlcSuccessFee(aliceChannel.channelState.ChanType, feeRate),
|
||||
)
|
||||
|
||||
// Helper method to check the current reported balance.
|
||||
@@ -6273,7 +6279,8 @@ func TestChanReserveLocalInitiatorDustHtlc(t *testing.T) {
|
||||
// limit (1300 sat). It is considered dust if the amount remaining
|
||||
// after paying the HTLC fee is below the dustlimit, so we choose a
|
||||
// size of 500+htlcFee.
|
||||
htlcSat := btcutil.Amount(500) + htlcTimeoutFee(
|
||||
htlcSat := btcutil.Amount(500) + HtlcTimeoutFee(
|
||||
aliceChannel.channelState.ChanType,
|
||||
chainfee.SatPerKWeight(
|
||||
aliceChannel.channelState.LocalCommitment.FeePerKw,
|
||||
),
|
||||
|
||||
+225
-39
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
@@ -13,6 +14,9 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// anchorSize is the constant anchor output size.
|
||||
const anchorSize = btcutil.Amount(330)
|
||||
|
||||
// CommitmentKeyRing holds all derived keys needed to construct commitment and
|
||||
// HTLC transactions. The keys are derived differently depending whether the
|
||||
// commitment transaction is ours or the remote peer's. Private keys associated
|
||||
@@ -183,13 +187,34 @@ type ScriptInfo struct {
|
||||
|
||||
// CommitScriptToRemote creates the script that will pay to the non-owner of
|
||||
// the commitment transaction, adding a delay to the script based on the
|
||||
// channel type.
|
||||
func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32,
|
||||
key *btcec.PublicKey) (*ScriptInfo, error) {
|
||||
// channel type. The second return value is the CSV deleay of the output
|
||||
// script, what must be satisfied in order to spend the output.
|
||||
func CommitScriptToRemote(chanType channeldb.ChannelType,
|
||||
key *btcec.PublicKey) (*ScriptInfo, uint32, error) {
|
||||
|
||||
// If this channel type has anchors, we derive the delayed to_remote
|
||||
// script.
|
||||
if chanType.HasAnchors() {
|
||||
script, err := input.CommitScriptToRemoteConfirmed(key)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
p2wsh, err := input.WitnessScriptHash(script)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return &ScriptInfo{
|
||||
PkScript: p2wsh,
|
||||
WitnessScript: script,
|
||||
}, 1, nil
|
||||
}
|
||||
|
||||
// Otherwise the to_remote will be a simple p2wkh.
|
||||
p2wkh, err := input.CommitScriptUnencumbered(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Since this is a regular P2WKH, the WitnessScipt and PkScript should
|
||||
@@ -197,7 +222,100 @@ func CommitScriptToRemote(_ channeldb.ChannelType, csvTimeout uint32,
|
||||
return &ScriptInfo{
|
||||
WitnessScript: p2wkh,
|
||||
PkScript: p2wkh,
|
||||
}, nil
|
||||
}, 0, nil
|
||||
}
|
||||
|
||||
// HtlcSigHashType returns the sighash type to use for HTLC success and timeout
|
||||
// transactions given the channel type.
|
||||
func HtlcSigHashType(chanType channeldb.ChannelType) txscript.SigHashType {
|
||||
if chanType.HasAnchors() {
|
||||
return txscript.SigHashSingle | txscript.SigHashAnyOneCanPay
|
||||
}
|
||||
|
||||
return txscript.SigHashAll
|
||||
}
|
||||
|
||||
// HtlcSecondLevelInputSequence dictates the sequence number we must use on the
|
||||
// input to a second level HTLC transaction.
|
||||
func HtlcSecondLevelInputSequence(chanType channeldb.ChannelType) uint32 {
|
||||
if chanType.HasAnchors() {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// CommitWeight returns the base commitment weight before adding HTLCs.
|
||||
func CommitWeight(chanType channeldb.ChannelType) int64 {
|
||||
// If this commitment has anchors, it will be slightly heavier.
|
||||
if chanType.HasAnchors() {
|
||||
return input.AnchorCommitWeight
|
||||
}
|
||||
|
||||
return input.CommitWeight
|
||||
}
|
||||
|
||||
// HtlcTimeoutFee returns the fee in satoshis required for an HTLC timeout
|
||||
// transaction based on the current fee rate.
|
||||
func HtlcTimeoutFee(chanType channeldb.ChannelType,
|
||||
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
|
||||
if chanType.HasAnchors() {
|
||||
return feePerKw.FeeForWeight(input.HtlcTimeoutWeightConfirmed)
|
||||
}
|
||||
|
||||
return feePerKw.FeeForWeight(input.HtlcTimeoutWeight)
|
||||
}
|
||||
|
||||
// HtlcSuccessFee returns the fee in satoshis required for an HTLC success
|
||||
// transaction based on the current fee rate.
|
||||
func HtlcSuccessFee(chanType channeldb.ChannelType,
|
||||
feePerKw chainfee.SatPerKWeight) btcutil.Amount {
|
||||
|
||||
if chanType.HasAnchors() {
|
||||
return feePerKw.FeeForWeight(input.HtlcSuccessWeightConfirmed)
|
||||
}
|
||||
return feePerKw.FeeForWeight(input.HtlcSuccessWeight)
|
||||
}
|
||||
|
||||
// CommitScriptAnchors return the scripts to use for the local and remote
|
||||
// anchor.
|
||||
func CommitScriptAnchors(localChanCfg,
|
||||
remoteChanCfg *channeldb.ChannelConfig) (*ScriptInfo,
|
||||
*ScriptInfo, error) {
|
||||
|
||||
// Helper to create anchor ScriptInfo from key.
|
||||
anchorScript := func(key *btcec.PublicKey) (*ScriptInfo, error) {
|
||||
script, err := input.CommitScriptAnchor(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
scriptHash, err := input.WitnessScriptHash(script)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &ScriptInfo{
|
||||
PkScript: scriptHash,
|
||||
WitnessScript: script,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Get the script used for the anchor output spendable by the local
|
||||
// node.
|
||||
localAnchor, err := anchorScript(localChanCfg.MultiSigKey.PubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// And the anchor spemdable by the remote node.
|
||||
remoteAnchor, err := anchorScript(remoteChanCfg.MultiSigKey.PubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return localAnchor, remoteAnchor, nil
|
||||
}
|
||||
|
||||
// CommitmentBuilder is a type that wraps the type of channel we are dealing
|
||||
@@ -216,6 +334,11 @@ type CommitmentBuilder struct {
|
||||
|
||||
// NewCommitmentBuilder creates a new CommitmentBuilder from chanState.
|
||||
func NewCommitmentBuilder(chanState *channeldb.OpenChannel) *CommitmentBuilder {
|
||||
// The anchor channel type MUST be tweakless.
|
||||
if chanState.ChanType.HasAnchors() && !chanState.ChanType.IsTweakless() {
|
||||
panic("invalid channel type combination")
|
||||
}
|
||||
|
||||
return &CommitmentBuilder{
|
||||
chanState: chanState,
|
||||
obfuscator: createStateHintObfuscator(chanState),
|
||||
@@ -248,8 +371,9 @@ type unsignedCommitmentTx struct {
|
||||
// fee is the total fee of the commitment transaction.
|
||||
fee btcutil.Amount
|
||||
|
||||
// ourBalance|theirBalance is the balances of this commitment. This can
|
||||
// be different than the balances before creating the commitment
|
||||
// ourBalance|theirBalance are the balances of this commitment *after*
|
||||
// subtracting commitment fees and anchor outputs. This can be
|
||||
// different than the balances before creating the commitment
|
||||
// transaction as one party must pay the commitment fee.
|
||||
ourBalance lnwire.MilliSatoshi
|
||||
theirBalance lnwire.MilliSatoshi
|
||||
@@ -258,7 +382,7 @@ type unsignedCommitmentTx struct {
|
||||
// createUnsignedCommitmentTx generates the unsigned commitment transaction for
|
||||
// a commitment view and returns it as part of the unsignedCommitmentTx. The
|
||||
// passed in balances should be balances *before* subtracting any commitment
|
||||
// fees.
|
||||
// fees, but after anchor outputs.
|
||||
func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
theirBalance lnwire.MilliSatoshi, isOurs bool,
|
||||
feePerKw chainfee.SatPerKWeight, height uint64,
|
||||
@@ -272,18 +396,20 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
|
||||
numHTLCs := int64(0)
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
numHTLCs++
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -294,7 +420,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
// on its total weight. Once we have the total weight, we'll multiply
|
||||
// by the current fee-per-kw, then divide by 1000 to get the proper
|
||||
// fee.
|
||||
totalCommitWeight := input.CommitWeight + (input.HTLCWeight * numHTLCs)
|
||||
totalCommitWeight := CommitWeight(cb.chanState.ChanType) +
|
||||
input.HTLCWeight*numHTLCs
|
||||
|
||||
// With the weight known, we can now calculate the commitment fee,
|
||||
// ensuring that we account for any dust outputs trimmed above.
|
||||
@@ -333,12 +460,14 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
|
||||
&cb.chanState.LocalChanCfg, &cb.chanState.RemoteChanCfg,
|
||||
ourBalance.ToSatoshis(), theirBalance.ToSatoshis(),
|
||||
numHTLCs,
|
||||
)
|
||||
} else {
|
||||
commitTx, err = CreateCommitTx(
|
||||
cb.chanState.ChanType, fundingTxIn(cb.chanState), keyRing,
|
||||
&cb.chanState.RemoteChanCfg, &cb.chanState.LocalChanCfg,
|
||||
theirBalance.ToSatoshis(), ourBalance.ToSatoshis(),
|
||||
numHTLCs,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
@@ -356,24 +485,34 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
// purposes of sorting.
|
||||
cltvs := make([]uint32, len(commitTx.TxOut))
|
||||
for _, htlc := range filteredHTLCView.ourUpdates {
|
||||
if htlcIsDust(false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, false, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := addHTLC(commitTx, isOurs, false, htlc, keyRing)
|
||||
err := addHTLC(
|
||||
commitTx, isOurs, false, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cltvs = append(cltvs, htlc.Timeout)
|
||||
}
|
||||
for _, htlc := range filteredHTLCView.theirUpdates {
|
||||
if htlcIsDust(true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit) {
|
||||
if htlcIsDust(
|
||||
cb.chanState.ChanType, true, isOurs, feePerKw,
|
||||
htlc.Amount.ToSatoshis(), dustLimit,
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
||||
err := addHTLC(commitTx, isOurs, true, htlc, keyRing)
|
||||
err := addHTLC(
|
||||
commitTx, isOurs, true, htlc, keyRing,
|
||||
cb.chanState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -430,7 +569,8 @@ func (cb *CommitmentBuilder) createUnsignedCommitmentTx(ourBalance,
|
||||
func CreateCommitTx(chanType channeldb.ChannelType,
|
||||
fundingOutput wire.TxIn, keyRing *CommitmentKeyRing,
|
||||
localChanCfg, remoteChanCfg *channeldb.ChannelConfig,
|
||||
amountToLocal, amountToRemote btcutil.Amount) (*wire.MsgTx, error) {
|
||||
amountToLocal, amountToRemote btcutil.Amount,
|
||||
numHTLCs int64) (*wire.MsgTx, error) {
|
||||
|
||||
// First, we create the script for the delayed "pay-to-self" output.
|
||||
// This output has 2 main redemption clauses: either we can redeem the
|
||||
@@ -452,8 +592,8 @@ func CreateCommitTx(chanType channeldb.ChannelType,
|
||||
}
|
||||
|
||||
// Next, we create the script paying to the remote.
|
||||
toRemoteScript, err := CommitScriptToRemote(
|
||||
chanType, uint32(remoteChanCfg.CsvDelay), keyRing.ToRemoteKey,
|
||||
toRemoteScript, _, err := CommitScriptToRemote(
|
||||
chanType, keyRing.ToRemoteKey,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -466,26 +606,58 @@ func CreateCommitTx(chanType channeldb.ChannelType,
|
||||
commitTx.AddTxIn(&fundingOutput)
|
||||
|
||||
// Avoid creating dust outputs within the commitment transaction.
|
||||
if amountToLocal >= localChanCfg.DustLimit {
|
||||
localOutput := amountToLocal >= localChanCfg.DustLimit
|
||||
if localOutput {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: toLocalScriptHash,
|
||||
Value: int64(amountToLocal),
|
||||
})
|
||||
}
|
||||
if amountToRemote >= localChanCfg.DustLimit {
|
||||
|
||||
remoteOutput := amountToRemote >= localChanCfg.DustLimit
|
||||
if remoteOutput {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: toRemoteScript.PkScript,
|
||||
Value: int64(amountToRemote),
|
||||
})
|
||||
}
|
||||
|
||||
// If this channel type has anchors, we'll also add those.
|
||||
if chanType.HasAnchors() {
|
||||
localAnchor, remoteAnchor, err := CommitScriptAnchors(
|
||||
localChanCfg, remoteChanCfg,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Add local anchor output only if we have a commitment output
|
||||
// or there are HTLCs.
|
||||
if localOutput || numHTLCs > 0 {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: localAnchor.PkScript,
|
||||
Value: int64(anchorSize),
|
||||
})
|
||||
}
|
||||
|
||||
// Add anchor output to remote only if they have a commitment
|
||||
// output or there are HTLCs.
|
||||
if remoteOutput || numHTLCs > 0 {
|
||||
commitTx.AddTxOut(&wire.TxOut{
|
||||
PkScript: remoteAnchor.PkScript,
|
||||
Value: int64(anchorSize),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return commitTx, nil
|
||||
}
|
||||
|
||||
// genHtlcScript generates the proper P2WSH public key scripts for the HTLC
|
||||
// output modified by two-bits denoting if this is an incoming HTLC, and if the
|
||||
// HTLC is being applied to their commitment transaction or ours.
|
||||
func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
|
||||
func genHtlcScript(chanType channeldb.ChannelType, isIncoming, ourCommit bool,
|
||||
timeout uint32, rHash [32]byte,
|
||||
keyRing *CommitmentKeyRing) ([]byte, []byte, error) {
|
||||
|
||||
var (
|
||||
@@ -493,6 +665,12 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
|
||||
err error
|
||||
)
|
||||
|
||||
// Choose scripts based on channel type.
|
||||
confirmedHtlcSpends := false
|
||||
if chanType.HasAnchors() {
|
||||
confirmedHtlcSpends = true
|
||||
}
|
||||
|
||||
// Generate the proper redeem scripts for the HTLC output modified by
|
||||
// two-bits denoting if this is an incoming HTLC, and if the HTLC is
|
||||
// being applied to their commitment transaction or ours.
|
||||
@@ -501,30 +679,37 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
|
||||
// transaction. So we need to use the receiver's version of HTLC the
|
||||
// script.
|
||||
case isIncoming && ourCommit:
|
||||
witnessScript, err = input.ReceiverHTLCScript(timeout,
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:])
|
||||
witnessScript, err = input.ReceiverHTLCScript(
|
||||
timeout, keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// We're being paid via an HTLC by the remote party, and the HTLC is
|
||||
// being added to their commitment transaction, so we use the sender's
|
||||
// version of the HTLC script.
|
||||
case isIncoming && !ourCommit:
|
||||
witnessScript, err = input.SenderHTLCScript(keyRing.RemoteHtlcKey,
|
||||
keyRing.LocalHtlcKey, keyRing.RevocationKey, rHash[:])
|
||||
witnessScript, err = input.SenderHTLCScript(
|
||||
keyRing.RemoteHtlcKey, keyRing.LocalHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// We're sending an HTLC which is being added to our commitment
|
||||
// transaction. Therefore, we need to use the sender's version of the
|
||||
// HTLC script.
|
||||
case !isIncoming && ourCommit:
|
||||
witnessScript, err = input.SenderHTLCScript(keyRing.LocalHtlcKey,
|
||||
keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:])
|
||||
witnessScript, err = input.SenderHTLCScript(
|
||||
keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
|
||||
// Finally, we're paying the remote party via an HTLC, which is being
|
||||
// added to their commitment transaction. Therefore, we use the
|
||||
// receiver's version of the HTLC script.
|
||||
case !isIncoming && !ourCommit:
|
||||
witnessScript, err = input.ReceiverHTLCScript(timeout, keyRing.LocalHtlcKey,
|
||||
keyRing.RemoteHtlcKey, keyRing.RevocationKey, rHash[:])
|
||||
witnessScript, err = input.ReceiverHTLCScript(
|
||||
timeout, keyRing.LocalHtlcKey, keyRing.RemoteHtlcKey,
|
||||
keyRing.RevocationKey, rHash[:], confirmedHtlcSpends,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -549,13 +734,14 @@ func genHtlcScript(isIncoming, ourCommit bool, timeout uint32, rHash [32]byte,
|
||||
// the descriptor itself.
|
||||
func addHTLC(commitTx *wire.MsgTx, ourCommit bool,
|
||||
isIncoming bool, paymentDesc *PaymentDescriptor,
|
||||
keyRing *CommitmentKeyRing) error {
|
||||
keyRing *CommitmentKeyRing, chanType channeldb.ChannelType) error {
|
||||
|
||||
timeout := paymentDesc.Timeout
|
||||
rHash := paymentDesc.RHash
|
||||
|
||||
p2wsh, witnessScript, err := genHtlcScript(isIncoming, ourCommit,
|
||||
timeout, rHash, keyRing)
|
||||
p2wsh, witnessScript, err := genHtlcScript(
|
||||
chanType, isIncoming, ourCommit, timeout, rHash, keyRing,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -700,7 +700,8 @@ func testCancelNonExistentReservation(miner *rpctest.Harness,
|
||||
// Create our own reservation, give it some ID.
|
||||
res, err := lnwallet.NewChannelReservation(
|
||||
10000, 10000, feePerKw, alice, 22, 10, &testHdSeed,
|
||||
lnwire.FFAnnounceChannel, true, nil, [32]byte{},
|
||||
lnwire.FFAnnounceChannel, lnwallet.CommitmentTypeTweakless,
|
||||
nil, [32]byte{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create res: %v", err)
|
||||
@@ -738,7 +739,7 @@ func testReservationInitiatorBalanceBelowDustCancel(miner *rpctest.Harness,
|
||||
FundingFeePerKw: 1000,
|
||||
PushMSat: 0,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: true,
|
||||
CommitType: lnwallet.CommitmentTypeTweakless,
|
||||
}
|
||||
_, err = alice.InitChannelReservation(req)
|
||||
switch {
|
||||
@@ -793,7 +794,8 @@ func assertContributionInitPopulated(t *testing.T, c *lnwallet.ChannelContributi
|
||||
}
|
||||
|
||||
func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
alice, bob *lnwallet.LightningWallet, t *testing.T, tweakless bool,
|
||||
alice, bob *lnwallet.LightningWallet, t *testing.T,
|
||||
commitType lnwallet.CommitmentType,
|
||||
aliceChanFunder chanfunding.Assembler,
|
||||
fetchFundingTx func() *wire.MsgTx, pendingChanID [32]byte) {
|
||||
|
||||
@@ -823,7 +825,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
FundingFeePerKw: feePerKw,
|
||||
PushMSat: pushAmt,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: tweakless,
|
||||
CommitType: commitType,
|
||||
ChanFunder: aliceChanFunder,
|
||||
}
|
||||
aliceChanReservation, err := alice.InitChannelReservation(aliceReq)
|
||||
@@ -874,7 +876,7 @@ func testSingleFunderReservationWorkflow(miner *rpctest.Harness,
|
||||
FundingFeePerKw: feePerKw,
|
||||
PushMSat: pushAmt,
|
||||
Flags: lnwire.FFAnnounceChannel,
|
||||
Tweakless: tweakless,
|
||||
CommitType: commitType,
|
||||
}
|
||||
bobChanReservation, err := bob.InitChannelReservation(bobReq)
|
||||
if err != nil {
|
||||
@@ -2543,7 +2545,8 @@ var walletTests = []walletTestCase{
|
||||
bob *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
testSingleFunderReservationWorkflow(
|
||||
miner, alice, bob, t, false, nil, nil,
|
||||
miner, alice, bob, t,
|
||||
lnwallet.CommitmentTypeLegacy, nil, nil,
|
||||
[32]byte{},
|
||||
)
|
||||
},
|
||||
@@ -2554,7 +2557,8 @@ var walletTests = []walletTestCase{
|
||||
bob *lnwallet.LightningWallet, t *testing.T) {
|
||||
|
||||
testSingleFunderReservationWorkflow(
|
||||
miner, alice, bob, t, true, nil, nil,
|
||||
miner, alice, bob, t,
|
||||
lnwallet.CommitmentTypeTweakless, nil, nil,
|
||||
[32]byte{},
|
||||
)
|
||||
},
|
||||
@@ -2809,8 +2813,8 @@ func testSingleFunderExternalFundingTx(miner *rpctest.Harness,
|
||||
// pending channel ID generated above to allow Alice and Bob to track
|
||||
// the funding flow externally.
|
||||
testSingleFunderReservationWorkflow(
|
||||
miner, alice, bob, t, true, aliceExternalFunder,
|
||||
func() *wire.MsgTx {
|
||||
miner, alice, bob, t, lnwallet.CommitmentTypeTweakless,
|
||||
aliceExternalFunder, func() *wire.MsgTx {
|
||||
return fundingTx
|
||||
}, pendingChanID,
|
||||
)
|
||||
|
||||
+68
-3
@@ -15,6 +15,39 @@ import (
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
)
|
||||
|
||||
// CommitmentType is an enum indicating the commitment type we should use for
|
||||
// the channel we are opening.
|
||||
type CommitmentType int
|
||||
|
||||
const (
|
||||
// CommitmentTypeLegacy is the legacy commitment format with a tweaked
|
||||
// to_remote key.
|
||||
CommitmentTypeLegacy = iota
|
||||
|
||||
// CommitmentTypeTweakless is a newer commitment format where the
|
||||
// to_remote key is static.
|
||||
CommitmentTypeTweakless
|
||||
|
||||
// CommitmentTypeAnchors is a commitment type that is tweakless, and
|
||||
// has extra anchor ouputs in order to bump the fee of the commitment
|
||||
// transaction.
|
||||
CommitmentTypeAnchors
|
||||
)
|
||||
|
||||
// String returns the name of the CommitmentType.
|
||||
func (c CommitmentType) String() string {
|
||||
switch c {
|
||||
case CommitmentTypeLegacy:
|
||||
return "legacy"
|
||||
case CommitmentTypeTweakless:
|
||||
return "tweakless"
|
||||
case CommitmentTypeAnchors:
|
||||
return "anchors"
|
||||
default:
|
||||
return "invalid"
|
||||
}
|
||||
}
|
||||
|
||||
// ChannelContribution is the primary constituent of the funding workflow
|
||||
// within lnwallet. Each side first exchanges their respective contributions
|
||||
// along with channel specific parameters like the min fee/KB. Once
|
||||
@@ -136,7 +169,7 @@ type ChannelReservation struct {
|
||||
func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
commitFeePerKw chainfee.SatPerKWeight, wallet *LightningWallet,
|
||||
id uint64, pushMSat lnwire.MilliSatoshi, chainHash *chainhash.Hash,
|
||||
flags lnwire.FundingFlag, tweaklessCommit bool,
|
||||
flags lnwire.FundingFlag, commitType CommitmentType,
|
||||
fundingAssembler chanfunding.Assembler,
|
||||
pendingChanID [32]byte) (*ChannelReservation, error) {
|
||||
|
||||
@@ -146,12 +179,25 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
initiator bool
|
||||
)
|
||||
|
||||
commitFee := commitFeePerKw.FeeForWeight(input.CommitWeight)
|
||||
// Based on the channel type, we determine the initial commit weight
|
||||
// and fee.
|
||||
commitWeight := int64(input.CommitWeight)
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
commitWeight = input.AnchorCommitWeight
|
||||
}
|
||||
commitFee := commitFeePerKw.FeeForWeight(commitWeight)
|
||||
|
||||
localFundingMSat := lnwire.NewMSatFromSatoshis(localFundingAmt)
|
||||
// TODO(halseth): make method take remote funding amount directly
|
||||
// instead of inferring it from capacity and local amt.
|
||||
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
||||
|
||||
// The total fee paid by the initiator will be the commitment fee in
|
||||
// addition to the two anchor outputs.
|
||||
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
feeMSat += 2 * lnwire.NewMSatFromSatoshis(anchorSize)
|
||||
}
|
||||
|
||||
// If we're the responder to a single-funder reservation, then we have
|
||||
// no initial balance in the channel unless the remote party is pushing
|
||||
@@ -213,6 +259,16 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
)
|
||||
}
|
||||
|
||||
// Similarly we ensure their balance is reasonable if we are not the
|
||||
// initiator.
|
||||
if !initiator && theirBalance.ToSatoshis() <= 2*DefaultDustLimit() {
|
||||
return nil, ErrFunderBalanceDust(
|
||||
int64(commitFee),
|
||||
int64(theirBalance.ToSatoshis()),
|
||||
int64(2*DefaultDustLimit()),
|
||||
)
|
||||
}
|
||||
|
||||
// Next we'll set the channel type based on what we can ascertain about
|
||||
// the balances/push amount within the channel.
|
||||
var chanType channeldb.ChannelType
|
||||
@@ -221,7 +277,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
// non-zero push amt (there's no pushing for dual funder), then this is
|
||||
// a single-funder channel.
|
||||
if ourBalance == 0 || theirBalance == 0 || pushMSat != 0 {
|
||||
if tweaklessCommit {
|
||||
// Both the tweakless type and the anchor type is tweakless,
|
||||
// hence set the bit.
|
||||
if commitType == CommitmentTypeTweakless ||
|
||||
commitType == CommitmentTypeAnchors {
|
||||
|
||||
chanType |= channeldb.SingleFunderTweaklessBit
|
||||
} else {
|
||||
chanType |= channeldb.SingleFunderBit
|
||||
@@ -241,6 +301,11 @@ func NewChannelReservation(capacity, localFundingAmt btcutil.Amount,
|
||||
chanType |= channeldb.DualFunderBit
|
||||
}
|
||||
|
||||
// We are adding anchor outputs to our commitment.
|
||||
if commitType == CommitmentTypeAnchors {
|
||||
chanType |= channeldb.AnchorOutputsBit
|
||||
}
|
||||
|
||||
return &ChannelReservation{
|
||||
ourContribution: &ChannelContribution{
|
||||
FundingAmount: ourBalance.ToSatoshis(),
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcutil"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/input"
|
||||
)
|
||||
|
||||
@@ -44,8 +45,8 @@ var (
|
||||
// In order to spend the HTLC output, the witness for the passed transaction
|
||||
// should be:
|
||||
// * <0> <sender sig> <recvr sig> <preimage>
|
||||
func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
csvDelay uint32,
|
||||
func createHtlcSuccessTx(chanType channeldb.ChannelType,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
// Create a version two transaction (as the success version of this
|
||||
@@ -53,10 +54,13 @@ func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
successTx := wire.NewMsgTx(2)
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
successTx.AddTxIn(&wire.TxIn{
|
||||
// original HTLC on the sender's commitment transaction. Set the
|
||||
// sequence number based on the channel type.
|
||||
txin := &wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
Sequence: HtlcSecondLevelInputSequence(chanType),
|
||||
}
|
||||
successTx.AddTxIn(txin)
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
@@ -97,7 +101,8 @@ func createHtlcSuccessTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
// NOTE: The passed amount for the HTLC should take into account the required
|
||||
// fee rate at the time the HTLC was created. The fee should be able to
|
||||
// entirely pay for this (tiny: 1-in 1-out) transaction.
|
||||
func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
func createHtlcTimeoutTx(chanType channeldb.ChannelType,
|
||||
htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
cltvExpiry, csvDelay uint32,
|
||||
revocationKey, delayKey *btcec.PublicKey) (*wire.MsgTx, error) {
|
||||
|
||||
@@ -108,10 +113,13 @@ func createHtlcTimeoutTx(htlcOutput wire.OutPoint, htlcAmt btcutil.Amount,
|
||||
timeoutTx.LockTime = cltvExpiry
|
||||
|
||||
// The input to the transaction is the outpoint that creates the
|
||||
// original HTLC on the sender's commitment transaction.
|
||||
timeoutTx.AddTxIn(&wire.TxIn{
|
||||
// original HTLC on the sender's commitment transaction. Set the
|
||||
// sequence number based on the channel type.
|
||||
txin := &wire.TxIn{
|
||||
PreviousOutPoint: htlcOutput,
|
||||
})
|
||||
Sequence: HtlcSecondLevelInputSequence(chanType),
|
||||
}
|
||||
timeoutTx.AddTxIn(txin)
|
||||
|
||||
// Next, we'll generate the script used as the output for all second
|
||||
// level HTLC which forces a covenant w.r.t what can be done with all
|
||||
|
||||
@@ -851,9 +851,10 @@ func TestCommitmentAndHTLCTransactions(t *testing.T) {
|
||||
// Generate second-level HTLC transactions for HTLCs in
|
||||
// commitment tx.
|
||||
htlcResolutions, err := extractHtlcResolutions(
|
||||
chainfee.SatPerKWeight(test.commitment.FeePerKw), true, signer,
|
||||
htlcs, keys, &channel.channelState.LocalChanCfg,
|
||||
chainfee.SatPerKWeight(test.commitment.FeePerKw), true,
|
||||
signer, htlcs, keys, &channel.channelState.LocalChanCfg,
|
||||
&channel.channelState.RemoteChanCfg, commitTx.TxHash(),
|
||||
channel.channelState.ChanType,
|
||||
)
|
||||
if err != nil {
|
||||
t.Errorf("Case %d: Failed to extract HTLC resolutions: %v", i, err)
|
||||
@@ -1089,7 +1090,7 @@ func testSpendValidation(t *testing.T, tweakless bool) {
|
||||
}
|
||||
commitmentTx, err := CreateCommitTx(
|
||||
channelType, *fakeFundingTxIn, keyRing, aliceChanCfg,
|
||||
bobChanCfg, channelBalance, channelBalance,
|
||||
bobChanCfg, channelBalance, channelBalance, 0,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create commitment transaction: %v", nil)
|
||||
|
||||
+6
-6
@@ -98,9 +98,9 @@ type InitFundingReserveMsg struct {
|
||||
// output selected to fund the channel should satisfy.
|
||||
MinConfs int32
|
||||
|
||||
// Tweakless indicates if the channel should use the new tweakless
|
||||
// commitment format or not.
|
||||
Tweakless bool
|
||||
// CommitType indicates what type of commitment type the channel should
|
||||
// be using, like tweakless or anchors.
|
||||
CommitType CommitmentType
|
||||
|
||||
// ChanFunder is an optional channel funder that allows the caller to
|
||||
// control exactly how the channel funding is carried out. If not
|
||||
@@ -568,7 +568,7 @@ func (l *LightningWallet) handleFundingReserveRequest(req *InitFundingReserveMsg
|
||||
reservation, err := NewChannelReservation(
|
||||
capacity, localFundingAmt, req.CommitFeePerKw, l, id,
|
||||
req.PushMSat, l.Cfg.NetParams.GenesisHash, req.Flags,
|
||||
req.Tweakless, req.ChanFunder, req.PendingChanID,
|
||||
req.CommitType, req.ChanFunder, req.PendingChanID,
|
||||
)
|
||||
if err != nil {
|
||||
if fundingIntent != nil {
|
||||
@@ -784,7 +784,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
||||
|
||||
ourCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, localCommitmentKeys, ourChanCfg,
|
||||
theirChanCfg, localBalance, remoteBalance,
|
||||
theirChanCfg, localBalance, remoteBalance, 0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -797,7 +797,7 @@ func CreateCommitmentTxns(localBalance, remoteBalance btcutil.Amount,
|
||||
|
||||
theirCommitTx, err := CreateCommitTx(
|
||||
chanType, fundingTxIn, remoteCommitmentKeys, theirChanCfg,
|
||||
ourChanCfg, remoteBalance, localBalance,
|
||||
ourChanCfg, remoteBalance, localBalance, 0,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
Reference in new issue
Block a user