diff --git a/lnwallet/channel.go b/lnwallet/channel.go index 379242fc..0cfca8fe 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -1874,13 +1874,163 @@ func processRemoveEntry(htlc *PaymentDescriptor, ourBalance, *removeHeight = nextHeight } +// generateRemoteHtlcSigJobs generates a series of HTLC signature jobs for the +// sig pool, along with a channel that if closed, will cancel any jobs after +// they have been submitted to the sigPool. This method is to be used when +// generating a new commitment for the remote party. The jobs generated by the +// signature can be submitted to the sigPool to generate all the signatures +// asynchronously and in parallel. +// +// TODO(roasbeef): all keys will eventually be generated within the commitment +// itself +func genRemoteHtlcSigJobs(commitPoint *btcec.PublicKey, + localChanCfg, remoteChanCfg *channeldb.ChannelConfig, + remoteCommitView *commitment) ([]signJob, chan struct{}, error) { + + // TODO(roasbeef): make the below into a sig pool job as well + + // First, we'll generate all the keys required to generate the scripts + // for each HTLC output and transaction. + // + // TODO(roabseef): avoid re-calculating, put in commitment struct? + commitTweak := SingleTweakBytes(commitPoint, + localChanCfg.PaymentBasePoint) + revocationKey := DeriveRevocationPubkey( + localChanCfg.RevocationBasePoint, + commitPoint, + ) + remoteDelayKey := TweakPubKey(remoteChanCfg.DelayBasePoint, + commitPoint) + + txHash := remoteCommitView.txn.TxHash() + dustLimit := localChanCfg.DustLimit + feePerKw := remoteCommitView.feePerKw + + // With the keys generated, we'll make a slice with enough capacity to + // hold potentially all the HTLC's. The actual slice may be a bit + // smaller (than its total capacity) an some HTLC's may be dust. + numSigs := (len(remoteCommitView.incomingHTLCs) + + len(remoteCommitView.outgoingHTLCs)) + sigBatch := make([]signJob, 0, numSigs) + + var err error + cancelChan := make(chan struct{}) + + // For ech outgoing and incoming HTLC, if the HTLC isn't considered a + // 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, dustLimit) { + continue + } + + // If the HTLC isn't dust, then we'll create an empty sign job + // to add to the batch momentarily. + sigJob := signJob{} + sigJob.cancel = cancelChan + sigJob.resp = make(chan signJobResp, 1) + + // As this is an incoming HTLC and we're sinning the commitment + // transaction of the remote node, we'll need to generate an + // 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) + outputAmt := htlc.Amount - htlcFee + + // With the fee calculate, we can properly create the HTLC + // timeout transaction using the HTLC amount minus the fee. + op := wire.OutPoint{ + Hash: txHash, + Index: uint32(htlc.remoteOutputIndex), + } + sigJob.tx, err = createHtlcTimeoutTx(op, outputAmt, + htlc.Timeout, uint32(remoteChanCfg.CsvDelay), + revocationKey, remoteDelayKey) + if err != nil { + return nil, nil, err + } + + // Finally, we'll generate a sign descriptor to generate a + // signature to give to the remote party for this commitment + // transaction. Note we use the raw HTLC amount. + sigJob.signDesc = SignDescriptor{ + PubKey: localChanCfg.PaymentBasePoint, + SingleTweak: commitTweak, + WitnessScript: htlc.theirWitnessScript, + Output: &wire.TxOut{ + Value: int64(htlc.Amount), + }, + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes(sigJob.tx), + InputIndex: 0, + } + sigJob.outputIndex = htlc.remoteOutputIndex + + sigBatch = append(sigBatch, sigJob) + } + for _, htlc := range remoteCommitView.outgoingHTLCs { + if htlcIsDust(false, false, feePerKw, htlc.Amount, dustLimit) { + continue + } + + sigJob := signJob{} + sigJob.cancel = cancelChan + sigJob.resp = make(chan signJobResp, 1) + + // As this is an outgoing HTLC and we're signing the commitment + // transaction of the remote node, we'll need to generate an + // 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) + outputAmt := htlc.Amount - htlcFee + + // With the proper output amount calculated, we can now + // generate the success transaction using the remote party's + // CSV delay. + op := wire.OutPoint{ + Hash: txHash, + Index: uint32(htlc.remoteOutputIndex), + } + sigJob.tx, err = createHtlcSuccessTx(op, outputAmt, + uint32(remoteChanCfg.CsvDelay), revocationKey, + remoteDelayKey) + if err != nil { + return nil, nil, err + } + + // Finally, we'll generate a sign descriptor to generate a + // signature to give to the remote party for this commitment + // transaction. Note we use the raw HTLC amount. + sigJob.signDesc = SignDescriptor{ + PubKey: localChanCfg.PaymentBasePoint, + SingleTweak: commitTweak, + WitnessScript: htlc.theirWitnessScript, + Output: &wire.TxOut{ + Value: int64(htlc.Amount), + }, + HashType: txscript.SigHashAll, + SigHashes: txscript.NewTxSigHashes(sigJob.tx), + InputIndex: 0, + } + sigJob.outputIndex = htlc.remoteOutputIndex + + sigBatch = append(sigBatch, sigJob) + } + + return sigBatch, cancelChan, nil +} + // SignNextCommitment signs a new commitment which includes any previous // unsettled HTLCs, any new HTLCs, and any modifications to prior HTLCs // committed in previous commitment updates. Signing a new commitment // decrements the available revocation window by 1. After a successful method // call, the remote party's commitment chain is extended by a new commitment // which includes all updates to the HTLC log prior to this method invocation. -func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { +// +// TODO(roasbeef): update to detail second param +func (lc *LightningChannel) SignNextCommitment() (*btcec.Signature, []*btcec.Signature, error) { lc.Lock() defer lc.Unlock() @@ -1898,7 +2048,7 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { err := lc.validateCommitmentSanity(lc.remoteUpdateLog.ackedIndex, lc.localUpdateLog.logIndex, false, true, true) if err != nil { - return nil, err + return nil, nil, err } // Grab the next commitment point for the remote party. This well be @@ -1916,7 +2066,7 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { newCommitView, err := lc.fetchCommitmentView(true, lc.localUpdateLog.logIndex, lc.remoteUpdateLog.ackedIndex, remoteRevocationKey, remoteRevocationHash) if err != nil { - return nil, err + return nil, nil, err } walletLog.Tracef("ChannelPoint(%v): extending remote chain to height %v", @@ -1931,11 +2081,54 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { }), ) - // Sign their version of the new commitment transaction. - lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn) - sig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc) + // With the commitment view constructed, if there are any HTLC's, we'll + // 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(commitPoint, + lc.localChanCfg, lc.remoteChanCfg, newCommitView, + ) if err != nil { - return nil, err + return nil, nil, err + } + // TODO(roasbeef): make fully async? + lc.sigPool.SubmitSignBatch(sigBatch) + + // While the jobs are being carried out, we'll Sign their version of + // the new commitment transaction while we're waiting for the rest of + // the HTLC signatures to be processed. + lc.signDesc.SigHashes = txscript.NewTxSigHashes(newCommitView.txn) + rawSig, err := lc.signer.SignOutputRaw(newCommitView.txn, lc.signDesc) + if err != nil { + close(cancelChan) + return nil, nil, err + } + sig, err := btcec.ParseSignature(rawSig, btcec.S256()) + if err != nil { + close(cancelChan) + return nil, nil, err + } + + // We'll need to send over the signatures to the remote party in the + // order as they appear on the commitment transaction after BIP 69 + // sorting. + sortedSigs := sortableSignBatch(sigBatch) + sort.Sort(sortedSigs) + + // With the jobs sorted, we'll now iterate through all the responses to + // gather each of the signatures in order. + htlcSigs := make([]*btcec.Signature, 0, len(sigBatch)) + for _, htlcSigJob := range sortedSigs { + jobResp := <-htlcSigJob.resp + + // If an error occurred, then we'll cancel any other active + // jobs. + if jobResp.err != nil { + close(cancelChan) + return nil, nil, err + } + + htlcSigs = append(htlcSigs, jobResp.sig) } // Extend the remote commitment chain by one with the addition of our @@ -1961,7 +2154,7 @@ func (lc *LightningChannel) SignNextCommitment() ([]byte, error) { // properly track which changes have been ACK'd. lc.localUpdateLog.initiateTransition() - return sig, nil + return sig, htlcSigs, nil } // validateCommitmentSanity is used to validate that on current state the commitment