50f495fae1
This commit adds some more comments and checks to reservation.CommitConstraints, including making MinHTLC value one of the passed constraints. RemoteChanConstraints is also moved out of reservation.
541 lines
20 KiB
Go
541 lines
20 KiB
Go
package lnwallet
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
|
"github.com/roasbeef/btcd/btcec"
|
|
"github.com/roasbeef/btcd/chaincfg/chainhash"
|
|
"github.com/roasbeef/btcd/wire"
|
|
"github.com/roasbeef/btcutil"
|
|
)
|
|
|
|
// 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
|
|
// contributions have been exchanged, each side will then produce signatures
|
|
// for all their inputs to the funding transactions, and finally a signature
|
|
// for the other party's version of the commitment transaction.
|
|
type ChannelContribution struct {
|
|
// FundingOutpoint is the amount of funds contributed to the funding
|
|
// transaction.
|
|
FundingAmount btcutil.Amount
|
|
|
|
// Inputs to the funding transaction.
|
|
Inputs []*wire.TxIn
|
|
|
|
// ChangeOutputs are the Outputs to be used in the case that the total
|
|
// value of the funding inputs is greater than the total potential
|
|
// channel capacity.
|
|
ChangeOutputs []*wire.TxOut
|
|
|
|
// FirstCommitmentPoint is the first commitment point that will be used
|
|
// to create the revocation key in the first commitment transaction we
|
|
// send to the remote party.
|
|
FirstCommitmentPoint *btcec.PublicKey
|
|
|
|
// ChannelConfig is the concrete contribution that this node is
|
|
// offering to the channel. This includes all the various constraints
|
|
// such as the min HTLC, and also all the keys which will be used for
|
|
// the duration of the channel.
|
|
*channeldb.ChannelConfig
|
|
}
|
|
|
|
// toChanConfig returns the raw channel configuration generated by a node's
|
|
// contribution to the channel.
|
|
func (c *ChannelContribution) toChanConfig() channeldb.ChannelConfig {
|
|
return *c.ChannelConfig
|
|
}
|
|
|
|
// InputScript represents any script inputs required to redeem a previous
|
|
// output. This struct is used rather than just a witness, or scripSig in
|
|
// order to accommodate nested p2sh which utilizes both types of input scripts.
|
|
type InputScript struct {
|
|
Witness [][]byte
|
|
ScriptSig []byte
|
|
}
|
|
|
|
// ChannelReservation represents an intent to open a lightning payment channel
|
|
// a counterparty. The funding processes from reservation to channel opening is
|
|
// a 3-step process. In order to allow for full concurrency during the
|
|
// reservation workflow, resources consumed by a contribution are "locked"
|
|
// themselves. This prevents a number of race conditions such as two funding
|
|
// transactions double-spending the same input. A reservation can also be
|
|
// cancelled, which removes the resources from limbo, allowing another
|
|
// reservation to claim them.
|
|
//
|
|
// The reservation workflow consists of the following three steps:
|
|
// 1. lnwallet.InitChannelReservation
|
|
// * One requests the wallet to allocate the necessary resources for a
|
|
// channel reservation. These resources a put in limbo for the lifetime
|
|
// of a reservation.
|
|
// * Once completed the reservation will have the wallet's contribution
|
|
// accessible via the .OurContribution() method. This contribution
|
|
// contains the necessary items to allow the remote party to build both
|
|
// the funding, and commitment transactions.
|
|
// 2. ChannelReservation.ProcessContribution/ChannelReservation.ProcessSingleContribution
|
|
// * The counterparty presents their contribution to the payment channel.
|
|
// This allows us to build the funding, and commitment transactions
|
|
// ourselves.
|
|
// * We're now able to sign our inputs to the funding transactions, and
|
|
// the counterparty's version of the commitment transaction.
|
|
// * All signatures crafted by us, are now available via .OurSignatures().
|
|
// 3. ChannelReservation.CompleteReservation/ChannelReservation.CompleteReservationSingle
|
|
// * The final step in the workflow. The counterparty presents the
|
|
// signatures for all their inputs to the funding transaction, as well
|
|
// as a signature to our version of the commitment transaction.
|
|
// * We then verify the validity of all signatures before considering the
|
|
// channel "open".
|
|
type ChannelReservation struct {
|
|
// This mutex MUST be held when either reading or modifying any of the
|
|
// fields below.
|
|
sync.RWMutex
|
|
|
|
// fundingTx is the funding transaction for this pending channel.
|
|
fundingTx *wire.MsgTx
|
|
|
|
// In order of sorted inputs. Sorting is done in accordance
|
|
// to BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
|
ourFundingInputScripts []*InputScript
|
|
theirFundingInputScripts []*InputScript
|
|
|
|
// Our signature for their version of the commitment transaction.
|
|
ourCommitmentSig []byte
|
|
theirCommitmentSig []byte
|
|
|
|
ourContribution *ChannelContribution
|
|
theirContribution *ChannelContribution
|
|
|
|
partialState *channeldb.OpenChannel
|
|
nodeAddr net.Addr
|
|
|
|
// The ID of this reservation, used to uniquely track the reservation
|
|
// throughout its lifetime.
|
|
reservationID uint64
|
|
|
|
// pushMSat the amount of milli-satoshis that should be pushed to the
|
|
// responder of a single funding channel as part of the initial
|
|
// commitment state.
|
|
pushMSat lnwire.MilliSatoshi
|
|
|
|
// chanOpen houses a struct containing the channel and additional
|
|
// confirmation details will be sent on once the channel is considered
|
|
// 'open'. A channel is open once the funding transaction has reached a
|
|
// sufficient number of confirmations.
|
|
chanOpen chan *openChanDetails
|
|
chanOpenErr chan error
|
|
|
|
wallet *LightningWallet
|
|
}
|
|
|
|
// NewChannelReservation creates a new channel reservation. This function is
|
|
// used only internally by lnwallet. In order to concurrent safety, the
|
|
// creation of all channel reservations should be carried out via the
|
|
// lnwallet.InitChannelReservation interface.
|
|
func NewChannelReservation(capacity, fundingAmt, commitFeePerKw btcutil.Amount,
|
|
wallet *LightningWallet, id uint64, pushMSat lnwire.MilliSatoshi,
|
|
chainHash *chainhash.Hash,
|
|
flags lnwire.FundingFlag) (*ChannelReservation, error) {
|
|
|
|
var (
|
|
ourBalance lnwire.MilliSatoshi
|
|
theirBalance lnwire.MilliSatoshi
|
|
initiator bool
|
|
)
|
|
|
|
commitFee := btcutil.Amount(
|
|
(int64(commitFeePerKw) * CommitWeight) / 1000,
|
|
)
|
|
|
|
fundingMSat := lnwire.NewMSatFromSatoshis(fundingAmt)
|
|
capacityMSat := lnwire.NewMSatFromSatoshis(capacity)
|
|
feeMSat := lnwire.NewMSatFromSatoshis(commitFee)
|
|
|
|
// 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
|
|
// some funds to us within the first commitment state.
|
|
if fundingAmt == 0 {
|
|
ourBalance = pushMSat
|
|
theirBalance = capacityMSat - feeMSat - pushMSat
|
|
initiator = false
|
|
} else {
|
|
// TODO(roasbeef): need to rework fee structure in general and
|
|
// also when we "unlock" dual funder within the daemon
|
|
|
|
if capacity == fundingAmt {
|
|
// If we're initiating a single funder workflow, then
|
|
// we pay all the initial fees within the commitment
|
|
// transaction. We also deduct our balance by the
|
|
// amount pushed as part of the initial state.
|
|
ourBalance = capacityMSat - feeMSat - pushMSat
|
|
theirBalance = pushMSat
|
|
} else {
|
|
// Otherwise, this is a dual funder workflow where both
|
|
// slides split the amount funded and the commitment
|
|
// fee.
|
|
ourBalance = fundingMSat - (feeMSat / 2)
|
|
theirBalance = capacityMSat - fundingMSat - (feeMSat / 2) + pushMSat
|
|
}
|
|
|
|
initiator = true
|
|
}
|
|
|
|
// If we're the initiator and our starting balance within the channel
|
|
// after we take account of fees is below 2x the dust limit, then we'll
|
|
// reject this channel creation request.
|
|
//
|
|
// TODO(roasbeef): reject if 30% goes to fees? dust channel
|
|
if initiator && ourBalance.ToSatoshis() <= 2*DefaultDustLimit() {
|
|
return nil, fmt.Errorf("unable to init reservation, with "+
|
|
"fee=%v sat/kw, local output is too small: %v sat",
|
|
int64(commitFee), int64(ourBalance.ToSatoshis()))
|
|
}
|
|
|
|
// 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
|
|
|
|
// If either of the balances are zero at this point, or we have a
|
|
// 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 {
|
|
chanType = channeldb.SingleFunder
|
|
} else {
|
|
// Otherwise, this is a dual funder channel, and no side is
|
|
// technically the "initiator"
|
|
initiator = false
|
|
chanType = channeldb.DualFunder
|
|
}
|
|
|
|
return &ChannelReservation{
|
|
ourContribution: &ChannelContribution{
|
|
FundingAmount: ourBalance.ToSatoshis(),
|
|
ChannelConfig: &channeldb.ChannelConfig{},
|
|
},
|
|
theirContribution: &ChannelContribution{
|
|
FundingAmount: theirBalance.ToSatoshis(),
|
|
ChannelConfig: &channeldb.ChannelConfig{},
|
|
},
|
|
partialState: &channeldb.OpenChannel{
|
|
ChanType: chanType,
|
|
ChainHash: *chainHash,
|
|
IsPending: true,
|
|
IsInitiator: initiator,
|
|
ChannelFlags: flags,
|
|
Capacity: capacity,
|
|
LocalCommitment: channeldb.ChannelCommitment{
|
|
LocalBalance: ourBalance,
|
|
RemoteBalance: theirBalance,
|
|
FeePerKw: commitFeePerKw,
|
|
CommitFee: commitFee,
|
|
},
|
|
RemoteCommitment: channeldb.ChannelCommitment{
|
|
LocalBalance: ourBalance,
|
|
RemoteBalance: theirBalance,
|
|
FeePerKw: commitFeePerKw,
|
|
CommitFee: commitFee,
|
|
},
|
|
Db: wallet.Cfg.Database,
|
|
},
|
|
pushMSat: pushMSat,
|
|
reservationID: id,
|
|
chanOpen: make(chan *openChanDetails, 1),
|
|
chanOpenErr: make(chan error, 1),
|
|
wallet: wallet,
|
|
}, nil
|
|
}
|
|
|
|
// SetNumConfsRequired sets the number of confirmations that are required for
|
|
// the ultimate funding transaction before the channel can be considered open.
|
|
// This is distinct from the main reservation workflow as it allows
|
|
// implementations a bit more flexibility w.r.t to if the responder of the
|
|
// initiator sets decides the number of confirmations needed.
|
|
func (r *ChannelReservation) SetNumConfsRequired(numConfs uint16) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
r.partialState.NumConfsRequired = numConfs
|
|
}
|
|
|
|
// RegisterMinHTLC registers our desired amount for the smallest acceptable
|
|
// HTLC we'll accept within this channel. Any HTLC's that are extended which
|
|
// are below this value will SHOULD be rejected.
|
|
func (r *ChannelReservation) RegisterMinHTLC(minHTLC lnwire.MilliSatoshi) {
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
r.ourContribution.MinHTLC = minHTLC
|
|
}
|
|
|
|
// CommitConstraints takes the constraints that the remote party specifies for
|
|
// the type of commitments that we can generate for them. These constraints
|
|
// include several parameters that serve as flow control restricting the amount
|
|
// of satoshis that can be transferred in a single commitment. This function
|
|
// will also attempt to verify the constraints for sanity, returning an error
|
|
// if the parameters are seemed unsound.
|
|
func (r *ChannelReservation) CommitConstraints(csvDelay, maxHtlcs uint16,
|
|
maxValueInFlight, minHtlc lnwire.MilliSatoshi,
|
|
chanReserve btcutil.Amount) error {
|
|
|
|
r.Lock()
|
|
defer r.Unlock()
|
|
|
|
// Fail if we consider csvDelay excessively large.
|
|
// TODO(halseth): find a more scientific choice of value.
|
|
if csvDelay > 10000 {
|
|
return fmt.Errorf("csvDelay is too large: %d", csvDelay)
|
|
}
|
|
|
|
// Fail if we consider the channel reserve to be too large.
|
|
// We currently fail if it is greater than 20% of the
|
|
// channel capacity.
|
|
maxChanReserve := r.partialState.Capacity / 5
|
|
if chanReserve > maxChanReserve {
|
|
return fmt.Errorf("chanReserve is too large: %g",
|
|
chanReserve.ToBTC())
|
|
}
|
|
|
|
// Fail if the minimum HTLC value is too large. If this is
|
|
// too large, the channel won't be useful for sending small
|
|
// payments. This limit is currently set to maxValueInFlight,
|
|
// effictively letting the remote setting this as large as
|
|
// it wants.
|
|
// TODO(halseth): set a reasonable/dynamic value.
|
|
if minHtlc > maxValueInFlight {
|
|
return fmt.Errorf("minimum HTLC value is too large: %g",
|
|
r.ourContribution.MinHTLC.ToBTC())
|
|
}
|
|
|
|
// Fail if maxHtlcs is above the maximum allowed number of 483.
|
|
// This number is specified in BOLT-02.
|
|
if maxHtlcs > uint16(MaxHTLCNumber/2) {
|
|
return fmt.Errorf("maxHtlcs is too large: %d", maxHtlcs)
|
|
}
|
|
|
|
// Fail if we consider maxHtlcs too small. If this is too small
|
|
// we cannot offer many HTLCs to the remote.
|
|
const minNumHtlc = 5
|
|
if maxHtlcs < minNumHtlc {
|
|
return fmt.Errorf("maxHtlcs is too small: %d", maxHtlcs)
|
|
}
|
|
|
|
// Fail if we consider maxValueInFlight too small. We currently
|
|
// require the remote to at least allow minNumHtlc * minHtlc
|
|
// in flight.
|
|
if maxValueInFlight < minNumHtlc*minHtlc {
|
|
return fmt.Errorf("maxValueInFlight is too small: %g",
|
|
maxValueInFlight.ToBTC())
|
|
}
|
|
|
|
r.ourContribution.ChannelConfig.CsvDelay = csvDelay
|
|
r.ourContribution.ChannelConfig.ChanReserve = chanReserve
|
|
r.ourContribution.ChannelConfig.MaxAcceptedHtlcs = maxHtlcs
|
|
r.ourContribution.ChannelConfig.MaxPendingAmount = maxValueInFlight
|
|
r.ourContribution.ChannelConfig.MinHTLC = minHtlc
|
|
|
|
return nil
|
|
}
|
|
|
|
// OurContribution returns the wallet's fully populated contribution to the
|
|
// pending payment channel. See 'ChannelContribution' for further details
|
|
// regarding the contents of a contribution.
|
|
//
|
|
// NOTE: This SHOULD NOT be modified.
|
|
// TODO(roasbeef): make copy?
|
|
func (r *ChannelReservation) OurContribution() *ChannelContribution {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
|
|
return r.ourContribution
|
|
}
|
|
|
|
// ProcessContribution verifies the counterparty's contribution to the pending
|
|
// payment channel. As a result of this incoming message, lnwallet is able to
|
|
// build the funding transaction, and both commitment transactions. Once this
|
|
// message has been processed, all signatures to inputs to the funding
|
|
// transaction belonging to the wallet are available. Additionally, the wallet
|
|
// will generate a signature to the counterparty's version of the commitment
|
|
// transaction.
|
|
func (r *ChannelReservation) ProcessContribution(theirContribution *ChannelContribution) error {
|
|
errChan := make(chan error, 1)
|
|
|
|
r.wallet.msgChan <- &addContributionMsg{
|
|
pendingFundingID: r.reservationID,
|
|
contribution: theirContribution,
|
|
err: errChan,
|
|
}
|
|
|
|
return <-errChan
|
|
}
|
|
|
|
// ProcessSingleContribution verifies, and records the initiator's contribution
|
|
// to this pending single funder channel. Internally, no further action is
|
|
// taken other than recording the initiator's contribution to the single funder
|
|
// channel.
|
|
func (r *ChannelReservation) ProcessSingleContribution(theirContribution *ChannelContribution) error {
|
|
errChan := make(chan error, 1)
|
|
|
|
r.wallet.msgChan <- &addSingleContributionMsg{
|
|
pendingFundingID: r.reservationID,
|
|
contribution: theirContribution,
|
|
err: errChan,
|
|
}
|
|
|
|
return <-errChan
|
|
}
|
|
|
|
// TheirContribution returns the counterparty's pending contribution to the
|
|
// payment channel. See 'ChannelContribution' for further details regarding the
|
|
// contents of a contribution. This attribute will ONLY be available after a
|
|
// call to .ProcesContribution().
|
|
//
|
|
// NOTE: This SHOULD NOT be modified.
|
|
func (r *ChannelReservation) TheirContribution() *ChannelContribution {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.theirContribution
|
|
}
|
|
|
|
// OurSignatures retrieves the wallet's signatures to all inputs to the funding
|
|
// transaction belonging to itself, and also a signature for the counterparty's
|
|
// version of the commitment transaction. The signatures for the wallet's
|
|
// inputs to the funding transaction are returned in sorted order according to
|
|
// BIP-69: https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
|
//
|
|
// NOTE: These signatures will only be populated after a call to
|
|
// .ProcesContribution()
|
|
func (r *ChannelReservation) OurSignatures() ([]*InputScript, []byte) {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.ourFundingInputScripts, r.ourCommitmentSig
|
|
}
|
|
|
|
// CompleteReservation finalizes the pending channel reservation, transitioning
|
|
// from a pending payment channel, to an open payment channel. All passed
|
|
// signatures to the counterparty's inputs to the funding transaction will be
|
|
// fully verified. Signatures are expected to be passed in sorted order
|
|
// according to BIP-69:
|
|
// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki.
|
|
// Additionally, verification is performed in order to ensure that the
|
|
// counterparty supplied a valid signature to our version of the commitment
|
|
// transaction. Once this method returns, caller's should then call
|
|
// .WaitForChannelOpen() which will block until the funding transaction obtains
|
|
// the configured number of confirmations. Once the method unblocks, a
|
|
// LightningChannel instance is returned, marking the channel available for
|
|
// updates.
|
|
func (r *ChannelReservation) CompleteReservation(fundingInputScripts []*InputScript,
|
|
commitmentSig []byte) (*channeldb.OpenChannel, error) {
|
|
|
|
// TODO(roasbeef): add flag for watch or not?
|
|
errChan := make(chan error, 1)
|
|
completeChan := make(chan *channeldb.OpenChannel, 1)
|
|
|
|
r.wallet.msgChan <- &addCounterPartySigsMsg{
|
|
pendingFundingID: r.reservationID,
|
|
theirFundingInputScripts: fundingInputScripts,
|
|
theirCommitmentSig: commitmentSig,
|
|
completeChan: completeChan,
|
|
err: errChan,
|
|
}
|
|
|
|
return <-completeChan, <-errChan
|
|
}
|
|
|
|
// CompleteReservationSingle finalizes the pending single funder channel
|
|
// reservation. Using the funding outpoint of the constructed funding
|
|
// transaction, and the initiator's signature for our version of the commitment
|
|
// transaction, we are able to verify the correctness of our commitment
|
|
// transaction as crafted by the initiator. Once this method returns, our
|
|
// signature for the initiator's version of the commitment transaction is
|
|
// available via the .OurSignatures() method. As this method should only be
|
|
// called as a response to a single funder channel, only a commitment signature
|
|
// will be populated.
|
|
func (r *ChannelReservation) CompleteReservationSingle(fundingPoint *wire.OutPoint,
|
|
commitSig []byte) (*channeldb.OpenChannel, error) {
|
|
|
|
errChan := make(chan error, 1)
|
|
completeChan := make(chan *channeldb.OpenChannel, 1)
|
|
|
|
r.wallet.msgChan <- &addSingleFunderSigsMsg{
|
|
pendingFundingID: r.reservationID,
|
|
fundingOutpoint: fundingPoint,
|
|
theirCommitmentSig: commitSig,
|
|
completeChan: completeChan,
|
|
err: errChan,
|
|
}
|
|
|
|
return <-completeChan, <-errChan
|
|
}
|
|
|
|
// TheirSignatures returns the counterparty's signatures to all inputs to the
|
|
// funding transaction belonging to them, as well as their signature for the
|
|
// wallet's version of the commitment transaction. This methods is provided for
|
|
// additional verification, such as needed by tests.
|
|
//
|
|
// NOTE: These attributes will be unpopulated before a call to
|
|
// .CompleteReservation().
|
|
func (r *ChannelReservation) TheirSignatures() ([]*InputScript, []byte) {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.theirFundingInputScripts, r.theirCommitmentSig
|
|
}
|
|
|
|
// FinalFundingTx returns the finalized, fully signed funding transaction for
|
|
// this reservation.
|
|
//
|
|
// NOTE: If this reservation was created as the non-initiator to a single
|
|
// funding workflow, then the full funding transaction will not be available.
|
|
// Instead we will only have the final outpoint of the funding transaction.
|
|
func (r *ChannelReservation) FinalFundingTx() *wire.MsgTx {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return r.fundingTx
|
|
}
|
|
|
|
// FundingOutpoint returns the outpoint of the funding transaction.
|
|
//
|
|
// NOTE: The pointer returned will only be set once the .ProcessContribution()
|
|
// method is called in the case of the initiator of a single funder workflow,
|
|
// and after the .CompleteReservationSingle() method is called in the case of
|
|
// a responder to a single funder workflow.
|
|
func (r *ChannelReservation) FundingOutpoint() *wire.OutPoint {
|
|
r.RLock()
|
|
defer r.RUnlock()
|
|
return &r.partialState.FundingOutpoint
|
|
}
|
|
|
|
// Cancel abandons this channel reservation. This method should be called in
|
|
// the scenario that communications with the counterparty break down. Upon
|
|
// cancellation, all resources previously reserved for this pending payment
|
|
// channel are returned to the free pool, allowing subsequent reservations to
|
|
// utilize the now freed resources.
|
|
func (r *ChannelReservation) Cancel() error {
|
|
errChan := make(chan error, 1)
|
|
r.wallet.msgChan <- &fundingReserveCancelMsg{
|
|
pendingFundingID: r.reservationID,
|
|
err: errChan,
|
|
}
|
|
|
|
return <-errChan
|
|
}
|
|
|
|
// OpenChannelDetails wraps the finalized fully confirmed channel which
|
|
// resulted from a ChannelReservation instance with details concerning exactly
|
|
// _where_ in the chain the channel was ultimately opened.
|
|
type OpenChannelDetails struct {
|
|
// Channel is the active channel created by an instance of a
|
|
// ChannelReservation and the required funding workflow.
|
|
Channel *LightningChannel
|
|
|
|
// ConfirmationHeight is the block height within the chain that
|
|
// included the channel.
|
|
ConfirmationHeight uint32
|
|
|
|
// TransactionIndex is the index within the confirming block that the
|
|
// transaction resides.
|
|
TransactionIndex uint32
|
|
}
|