funding: update funding workflow to adhere to BOLT-0002

This commit updates the main single-funder funding workflow within the
fundingManager (initiated via the rpcserver or by a message from a
connected peer) to fully adhere to the funding protocol outlined in
BOLT-0002.

The major changes are as follows:
  * All messages modified to use the new funding messages in BOLT-0002.
  * The initiator of a funding workflow no longer decides how many
confirmations must elapse before the channel can be considered open.
  * Rather than each side specifying their desired CSV delay, both
sides now specify the CSV delay for the _other_ party.
This commit is contained in:
Olaoluwa Osuntokun committed 2017-07-30 17:51:57 -07:00
1 parent 216cc3f919
commit 07212588fc
2 files changed
+347 -224

No files matched your search

+339 -214
View File
@@ -18,7 +18,7 @@ import (
"github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/txscript" "github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcd/wire"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -27,6 +27,8 @@ import (
const ( const (
// TODO(roasbeef): tune // TODO(roasbeef): tune
msgBufferSize = 50 msgBufferSize = 50
defaultCsvDelay = 4
) )
// reservationWithCtx encapsulates a pending channel reservation. This wrapper // reservationWithCtx encapsulates a pending channel reservation. This wrapper
@@ -35,12 +37,15 @@ const (
// peer. Additionally, this struct houses a response and error channel which is // peer. Additionally, this struct houses a response and error channel which is
// used to respond to the caller in the case a channel workflow is initiated // used to respond to the caller in the case a channel workflow is initiated
// via a local signal such as RPC. // via a local signal such as RPC.
//
// TODO(roasbeef): actually use the context package // TODO(roasbeef): actually use the context package
// * deadlines, etc. // * deadlines, etc.
type reservationWithCtx struct { type reservationWithCtx struct {
reservation *lnwallet.ChannelReservation reservation *lnwallet.ChannelReservation
peerAddress *lnwire.NetAddress peerAddress *lnwire.NetAddress
chanAmt btcutil.Amount
updates chan *lnrpc.OpenStatusUpdate updates chan *lnrpc.OpenStatusUpdate
err chan error err chan error
} }
@@ -55,35 +60,35 @@ type initFundingMsg struct {
*openChanReq *openChanReq
} }
// fundingRequestMsg couples an lnwire.SingleFundingRequest message with the // fundingOpenMsg couples an lnwire.OpenChannel message with the peer who sent
// peer who sent the message. This allows the funding manager to queue a // the message. This allows the funding manager to queue a response directly to
// response directly to the peer, progressing the funding workflow. // the peer, progressing the funding workflow.
type fundingRequestMsg struct { type fundingOpenMsg struct {
msg *lnwire.SingleFundingRequest msg *lnwire.OpenChannel
peerAddress *lnwire.NetAddress peerAddress *lnwire.NetAddress
} }
// fundingResponseMsg couples an lnwire.SingleFundingResponse message with the // fundingAcceptMsg couples an lnwire.AcceptChannel message with the peer who
// peer who sent the message. This allows the funding manager to queue a // sent the message. This allows the funding manager to queue a response
// response directly to the peer, progressing the funding workflow. // directly to the peer, progressing the funding workflow.
type fundingResponseMsg struct { type fundingAcceptMsg struct {
msg *lnwire.SingleFundingResponse msg *lnwire.AcceptChannel
peerAddress *lnwire.NetAddress peerAddress *lnwire.NetAddress
} }
// fundingCompleteMsg couples an lnwire.SingleFundingComplete message with the // fundingCreatedMsg couples an lnwire.FundingCreated message with the peer who
// peer who sent the message. This allows the funding manager to queue a // sent the message. This allows the funding manager to queue a response
// response directly to the peer, progressing the funding workflow. // directly to the peer, progressing the funding workflow.
type fundingCompleteMsg struct { type fundingCreatedMsg struct {
msg *lnwire.SingleFundingComplete msg *lnwire.FundingCreated
peerAddress *lnwire.NetAddress peerAddress *lnwire.NetAddress
} }
// fundingSignCompleteMsg couples an lnwire.SingleFundingSignComplete message // fundingSignedMsg couples an lnwire.FundingSigned message with the peer who
// with the peer who sent the message. This allows the funding manager to // sent the message. This allows the funding manager to queue a response
// queue a response directly to the peer, progressing the funding workflow. // directly to the peer, progressing the funding workflow.
type fundingSignCompleteMsg struct { type fundingSignedMsg struct {
msg *lnwire.SingleFundingSignComplete msg *lnwire.FundingSigned
peerAddress *lnwire.NetAddress peerAddress *lnwire.NetAddress
} }
@@ -103,7 +108,8 @@ type fundingErrorMsg struct {
} }
// pendingChannels is a map instantiated per-peer which tracks all active // pendingChannels is a map instantiated per-peer which tracks all active
// pending single funded channels indexed by their pending channel identifier. // pending single funded channels indexed by their pending channel identifier,
// which is a set of 32-bytes generated via a CSPRNG.
type pendingChannels map[[32]byte]*reservationWithCtx type pendingChannels map[[32]byte]*reservationWithCtx
// serializedPubKey is used within the FundingManager's activeReservations list // serializedPubKey is used within the FundingManager's activeReservations list
@@ -137,7 +143,7 @@ type fundingConfig struct {
// ArbiterChan allows the FundingManager to notify the BreachArbiter // ArbiterChan allows the FundingManager to notify the BreachArbiter
// that a new channel has been created that should be observed to // that a new channel has been created that should be observed to
// ensure that the channel counterparty hasn't broadcasted an invalid // ensure that the channel counterparty hasn't broadcast an invalid
// commitment transaction. // commitment transaction.
ArbiterChan chan<- *lnwallet.LightningChannel ArbiterChan chan<- *lnwallet.LightningChannel
@@ -150,6 +156,9 @@ type fundingConfig struct {
// actual digest signed is the double sha-256 of the message. In the // actual digest signed is the double sha-256 of the message. In the
// case that the private key corresponding to the passed public key // case that the private key corresponding to the passed public key
// cannot be located, then an error is returned. // cannot be located, then an error is returned.
//
// TODO(roasbeef): should instead pass on this responsibility to a
// distinct sub-system?
SignMessage func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) SignMessage func(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error)
// SendAnnouncement is used by the FundingManager to announce newly // SendAnnouncement is used by the FundingManager to announce newly
@@ -177,6 +186,20 @@ type fundingConfig struct {
// DefaultRoutingPolicy is the default routing policy used when // DefaultRoutingPolicy is the default routing policy used when
// initially announcing channels. // initially announcing channels.
DefaultRoutingPolicy htlcswitch.ForwardingPolicy DefaultRoutingPolicy htlcswitch.ForwardingPolicy
// NumRequiredConfs is a function closure that helps the funding
// manager decide how many confirmations it should require for a
// channel extended to it. The function is able to take into account
// the amount of the channel, and any funds we'll be pushed in the
// process to determine how many confirmations we'll require.
NumRequiredConfs func(btcutil.Amount, btcutil.Amount) uint16
// RequiredRemoteDelay is a function that maps the total amount in a
// proposed channel to the CSV delay that we'll require for the remote
// party. Naturally a larger channel should require a higher CSV delay
// in order to give us more time to claim funds in the case of a
// contract breach.
RequiredRemoteDelay func(btcutil.Amount) uint16
} }
// fundingManager acts as an orchestrator/bridge between the wallet's // fundingManager acts as an orchestrator/bridge between the wallet's
@@ -206,11 +229,21 @@ type fundingManager struct {
nonceMtx sync.RWMutex nonceMtx sync.RWMutex
chanIDNonce uint64 chanIDNonce uint64
// channelReservations is a map which houses the state of all pending // activeReservations is a map which houses the state of all pending
// funding workflows. // funding workflows.
resMtx sync.RWMutex
activeReservations map[serializedPubKey]pendingChannels activeReservations map[serializedPubKey]pendingChannels
// signedReservations is a utility map that maps the permanent channel
// ID of a funding reservation to its temporary channel ID. This is
// required as mid funding flow, we switch to referencing the channel
// by its full channel ID once the commitment transactions have been
// signed by both parties.
signedReservations map[lnwire.ChannelID][32]byte
// resMtx guards both of the maps above to ensure that all access is
// goroutine stafe.
resMtx sync.RWMutex
// fundingMsgs is a channel which receives wrapped wire messages // fundingMsgs is a channel which receives wrapped wire messages
// related to funding workflow from outside peers. // related to funding workflow from outside peers.
fundingMsgs chan interface{} fundingMsgs chan interface{}
@@ -244,6 +277,7 @@ func newFundingManager(cfg fundingConfig) (*fundingManager, error) {
cfg: &cfg, cfg: &cfg,
chanIDKey: cfg.TempChanIDSeed, chanIDKey: cfg.TempChanIDSeed,
activeReservations: make(map[serializedPubKey]pendingChannels), activeReservations: make(map[serializedPubKey]pendingChannels),
signedReservations: make(map[lnwire.ChannelID][32]byte),
newChanBarriers: make(map[lnwire.ChannelID]chan struct{}), newChanBarriers: make(map[lnwire.ChannelID]chan struct{}),
fundingMsgs: make(chan interface{}, msgBufferSize), fundingMsgs: make(chan interface{}, msgBufferSize),
fundingRequests: make(chan *initFundingMsg, msgBufferSize), fundingRequests: make(chan *initFundingMsg, msgBufferSize),
@@ -268,7 +302,7 @@ func (f *fundingManager) Start() error {
// down. // down.
// TODO(roasbeef): store height that funding finished? // TODO(roasbeef): store height that funding finished?
// * would then replace call below // * would then replace call below
pendingChannels, err := f.cfg.Wallet.ChannelDB.FetchPendingChannels() pendingChannels, err := f.cfg.Wallet.Cfg.Database.FetchPendingChannels()
if err != nil { if err != nil {
return err return err
} }
@@ -280,8 +314,8 @@ func (f *fundingManager) Start() error {
for _, channel := range pendingChannels { for _, channel := range pendingChannels {
f.barrierMtx.Lock() f.barrierMtx.Lock()
fndgLog.Tracef("Loading pending ChannelPoint(%v), creating chan "+ fndgLog.Tracef("Loading pending ChannelPoint(%v), creating chan "+
"barrier", *channel.FundingOutpoint) "barrier", channel.FundingOutpoint)
chanID := lnwire.NewChanIDFromOutPoint(channel.FundingOutpoint) chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint)
f.newChanBarriers[chanID] = make(chan struct{}) f.newChanBarriers[chanID] = make(chan struct{})
f.barrierMtx.Unlock() f.barrierMtx.Unlock()
@@ -391,16 +425,17 @@ func (f *fundingManager) reservationCoordinator() {
for { for {
select { select {
case msg := <-f.fundingMsgs: case msg := <-f.fundingMsgs:
switch fmsg := msg.(type) { switch fmsg := msg.(type) {
case *fundingRequestMsg: case *fundingOpenMsg:
f.handleFundingRequest(fmsg) f.handleFundingOpen(fmsg)
case *fundingResponseMsg: case *fundingAcceptMsg:
f.handleFundingResponse(fmsg) f.handleFundingAccept(fmsg)
case *fundingCompleteMsg: case *fundingCreatedMsg:
f.handleFundingComplete(fmsg) f.handleFundingCreated(fmsg)
case *fundingSignCompleteMsg: case *fundingSignedMsg:
f.handleFundingSignComplete(fmsg) f.handleFundingSigned(fmsg)
case *fundingLockedMsg: case *fundingLockedMsg:
go f.handleFundingLocked(fmsg) go f.handleFundingLocked(fmsg)
case *fundingErrorMsg: case *fundingErrorMsg:
@@ -408,6 +443,7 @@ func (f *fundingManager) reservationCoordinator() {
} }
case req := <-f.fundingRequests: case req := <-f.fundingRequests:
f.handleInitFundingMsg(req) f.handleInitFundingMsg(req)
case req := <-f.queries: case req := <-f.queries:
switch msg := req.(type) { switch msg := req.(type) {
case *numPendingReq: case *numPendingReq:
@@ -424,7 +460,7 @@ func (f *fundingManager) reservationCoordinator() {
// handleNumPending handles a request for the total number of pending channels. // handleNumPending handles a request for the total number of pending channels.
func (f *fundingManager) handleNumPending(msg *numPendingReq) { func (f *fundingManager) handleNumPending(msg *numPendingReq) {
// TODO(roasbeef): remove this method? // TODO(roasbeef): remove this method?
dbPendingChannels, err := f.cfg.Wallet.ChannelDB.FetchPendingChannels() dbPendingChannels, err := f.cfg.Wallet.Cfg.Database.FetchPendingChannels()
if err != nil { if err != nil {
close(msg.resp) close(msg.resp)
msg.err <- err msg.err <- err
@@ -441,7 +477,7 @@ func (f *fundingManager) handleNumPending(msg *numPendingReq) {
func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) { func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) {
var pendingChannels []*pendingChannel var pendingChannels []*pendingChannel
dbPendingChannels, err := f.cfg.Wallet.ChannelDB.FetchPendingChannels() dbPendingChannels, err := f.cfg.Wallet.Cfg.Database.FetchPendingChannels()
if err != nil { if err != nil {
msg.resp <- nil msg.resp <- nil
msg.err <- err msg.err <- err
@@ -451,10 +487,10 @@ func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) {
for _, dbPendingChan := range dbPendingChannels { for _, dbPendingChan := range dbPendingChannels {
pendingChan := &pendingChannel{ pendingChan := &pendingChannel{
identityPub: dbPendingChan.IdentityPub, identityPub: dbPendingChan.IdentityPub,
channelPoint: dbPendingChan.ChanID, channelPoint: &dbPendingChan.FundingOutpoint,
capacity: dbPendingChan.Capacity, capacity: dbPendingChan.Capacity,
localBalance: dbPendingChan.OurBalance, localBalance: dbPendingChan.LocalBalance,
remoteBalance: dbPendingChan.TheirBalance, remoteBalance: dbPendingChan.RemoteBalance,
} }
pendingChannels = append(pendingChannels, pendingChan) pendingChannels = append(pendingChannels, pendingChan)
@@ -464,23 +500,28 @@ func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) {
msg.err <- nil msg.err <- nil
} }
// processFundingRequest sends a message to the fundingManager allowing it to // processFundingOpen sends a message to the fundingManager allowing it to
// initiate the new funding workflow with the source peer. // initiate the new funding workflow with the source peer.
func (f *fundingManager) processFundingRequest(msg *lnwire.SingleFundingRequest, func (f *fundingManager) processFundingOpen(msg *lnwire.OpenChannel,
peerAddress *lnwire.NetAddress) { peerAddress *lnwire.NetAddress) {
f.fundingMsgs <- &fundingRequestMsg{msg, peerAddress}
f.fundingMsgs <- &fundingOpenMsg{msg, peerAddress}
} }
// handleFundingRequest creates an initial 'ChannelReservation' within // handleFundingOpen creates an initial 'ChannelReservation' within the wallet,
// the wallet, then responds to the source peer with a single funder response // then responds to the source peer with an accept channel message progressing
// message progressing the funding workflow. // the funding workflow.
//
// TODO(roasbeef): add error chan to all, let channelManager handle // TODO(roasbeef): add error chan to all, let channelManager handle
// error+propagate // error+propagate
func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) { func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) {
// Check number of pending channels to be smaller than maximum allowed // Check number of pending channels to be smaller than maximum allowed
// number and send ErrorGeneric to remote peer if condition is violated. // number and send ErrorGeneric to remote peer if condition is
// violated.
peerIDKey := newSerializedKey(fmsg.peerAddress.IdentityKey) peerIDKey := newSerializedKey(fmsg.peerAddress.IdentityKey)
// TODO(roasbeef): modify to only accept a _single_ pending channel per
// block unless white listed
if len(f.activeReservations[peerIDKey]) >= cfg.MaxPendingChannels { if len(f.activeReservations[peerIDKey]) >= cfg.MaxPendingChannels {
errMsg := &lnwire.Error{ errMsg := &lnwire.Error{
ChanID: fmsg.msg.PendingChannelID, ChanID: fmsg.msg.PendingChannelID,
@@ -504,7 +545,6 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
fndgLog.Errorf("unable to query wallet: %v", err) fndgLog.Errorf("unable to query wallet: %v", err)
return return
} }
if !isSynced { if !isSynced {
errMsg := &lnwire.Error{ errMsg := &lnwire.Error{
ChanID: fmsg.msg.PendingChannelID, ChanID: fmsg.msg.PendingChannelID,
@@ -518,38 +558,50 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
return return
} }
// TODO(roasbeef): validate sanity of all params sent
msg := fmsg.msg msg := fmsg.msg
amt := msg.FundingAmount amt := msg.FundingAmount
delay := msg.CsvDelay
// TODO(roasbeef): error if funding flow already ongoing // TODO(roasbeef): error if funding flow already ongoing
fndgLog.Infof("Recv'd fundingRequest(amt=%v, push=%v, delay=%v, pendingId=%x) "+ fndgLog.Infof("Recv'd fundingRequest(amt=%v, push=%v, delay=%v, pendingId=%x) "+
"from peer(%x)", amt, msg.PushSatoshis, delay, msg.PendingChannelID, "from peer(%x)", amt, msg.PushAmount, msg.CsvDelay, msg.PendingChannelID,
fmsg.peerAddress.IdentityKey.SerializeCompressed()) fmsg.peerAddress.IdentityKey.SerializeCompressed())
// TODO(roasbeef): dust limit should be made parameter into funding mgr
// * will change on a per-chain basis
ourDustLimit := lnwallet.DefaultDustLimit()
theirDustlimit := msg.DustLimit
// Attempt to initialize a reservation within the wallet. If the wallet // Attempt to initialize a reservation within the wallet. If the wallet
// has insufficient resources to create the channel, then the // has insufficient resources to create the channel, then the
// reservation attempt may be rejected. Note that since we're on the // reservation attempt may be rejected. Note that since we're on the
// responding side of a single funder workflow, we don't commit any // responding side of a single funder workflow, we don't commit any
// funds to the channel ourselves. // funds to the channel ourselves.
//
// TODO(roasbeef): assuming this was an inbound connection, replace // TODO(roasbeef): assuming this was an inbound connection, replace
// port with default advertised port // port with default advertised port
chainHash := chainhash.Hash(msg.ChainHash)
reservation, err := f.cfg.Wallet.InitChannelReservation(amt, 0, reservation, err := f.cfg.Wallet.InitChannelReservation(amt, 0,
msg.PushAmount, btcutil.Amount(msg.FeePerKiloWeight),
fmsg.peerAddress.IdentityKey, fmsg.peerAddress.Address, fmsg.peerAddress.IdentityKey, fmsg.peerAddress.Address,
uint16(fmsg.msg.ConfirmationDepth), delay, ourDustLimit, &chainHash)
msg.PushSatoshis, msg.FeePerKw)
if err != nil { if err != nil {
// TODO(roasbeef): push ErrorGeneric message // TODO(roasbeef): push ErrorGeneric message
fndgLog.Errorf("Unable to initialize reservation: %v", err) fndgLog.Errorf("Unable to initialize reservation: %v", err)
return return
} }
reservation.SetTheirDustLimit(theirDustlimit) // As we're the responder, we get to specify the number of
// confirmations that we require before both of us consider the channel
// open. We'll use out mapping to derive the proper number of
// confirmations based on the amount of the channel, and also if any
// funds are being pushed to us.
numConfsReq := f.cfg.NumRequiredConfs(msg.FundingAmount, msg.PushAmount)
reservation.SetNumConfsRequired(numConfsReq)
// We'll also validate and apply all the constraints the initiating
// party is attempting to dictate for our commitment transaction.
reservation.RequireLocalDelay(msg.CsvDelay)
fndgLog.Infof("Requiring %v confirmations for pendingChan(%x): "+
"amt=%v, push_amt=%v", numConfsReq, fmsg.msg.PendingChannelID,
amt, msg.PushAmount)
// Once the reservation has been created successfully, we add it to // Once the reservation has been created successfully, we add it to
// this peers map of pending reservations to track this particular // this peers map of pending reservations to track this particular
@@ -560,6 +612,7 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
} }
f.activeReservations[peerIDKey][msg.PendingChannelID] = &reservationWithCtx{ f.activeReservations[peerIDKey][msg.PendingChannelID] = &reservationWithCtx{
reservation: reservation, reservation: reservation,
chanAmt: amt,
err: make(chan error, 1), err: make(chan error, 1),
peerAddress: fmsg.peerAddress, peerAddress: fmsg.peerAddress,
} }
@@ -573,24 +626,32 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
} }
} }
// With our portion of the reservation initialized, process the // Using the RequiredRemoteDelay closure, we'll compute the remote CSV
// initiators contribution to the channel. // delay we require given the total amount of funds within the channel.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(msg.DeliveryPkScript, remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt)
activeNetParams.Params)
if err != nil { // With our parameters set, we'll now process their contribution so we
fndgLog.Errorf("Unable to extract addresses from script: %v", err) // can move the funding workflow ahead.
cancelReservation() remoteContribution := &lnwallet.ChannelContribution{
return
}
contribution := &lnwallet.ChannelContribution{
FundingAmount: amt, FundingAmount: amt,
MultiSigKey: copyPubKey(msg.ChannelDerivationPoint), FirstCommitmentPoint: msg.FirstCommitmentPoint,
CommitKey: copyPubKey(msg.CommitmentKey), ChannelConfig: &channeldb.ChannelConfig{
DeliveryAddress: addrs[0], ChannelConstraints: channeldb.ChannelConstraints{
DustLimit: msg.DustLimit, DustLimit: msg.DustLimit,
CsvDelay: delay, MaxPendingAmount: msg.MaxValueInFlight,
ChanReserve: msg.ChannelReserve,
MinHTLC: btcutil.Amount(msg.HtlcMinimum),
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs,
},
CsvDelay: remoteCsvDelay,
MultiSigKey: copyPubKey(msg.FundingKey),
RevocationBasePoint: copyPubKey(msg.RevocationPoint),
PaymentBasePoint: copyPubKey(msg.PaymentPoint),
DelayBasePoint: copyPubKey(msg.DelayedPaymentPoint),
},
} }
if err := reservation.ProcessSingleContribution(contribution); err != nil { err = reservation.ProcessSingleContribution(remoteContribution)
if err != nil {
fndgLog.Errorf("unable to add contribution reservation: %v", err) fndgLog.Errorf("unable to add contribution reservation: %v", err)
cancelReservation() cancelReservation()
return return
@@ -602,35 +663,40 @@ func (f *fundingManager) handleFundingRequest(fmsg *fundingRequestMsg) {
// With the initiator's contribution recorded, respond with our // With the initiator's contribution recorded, respond with our
// contribution in the next message of the workflow. // contribution in the next message of the workflow.
ourContribution := reservation.OurContribution() ourContribution := reservation.OurContribution()
deliveryScript, err := txscript.PayToAddrScript(ourContribution.DeliveryAddress) fundingAccept := lnwire.AcceptChannel{
if err != nil { PendingChannelID: msg.PendingChannelID,
fndgLog.Errorf("unable to convert address to pkscript: %v", err) DustLimit: ourContribution.DustLimit,
cancelReservation() MaxValueInFlight: ourContribution.MaxPendingAmount,
return ChannelReserve: ourContribution.ChanReserve,
MinAcceptDepth: uint32(numConfsReq),
HtlcMinimum: uint32(ourContribution.MinHTLC),
CsvDelay: uint16(remoteCsvDelay),
FundingKey: ourContribution.MultiSigKey,
RevocationPoint: ourContribution.RevocationBasePoint,
PaymentPoint: ourContribution.PaymentBasePoint,
DelayedPaymentPoint: ourContribution.DelayBasePoint,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
} }
fundingResp := lnwire.NewSingleFundingResponse(msg.PendingChannelID, err = f.cfg.SendToPeer(fmsg.peerAddress.IdentityKey, &fundingAccept)
ourContribution.RevocationKey, ourContribution.CommitKey, if err != nil {
ourContribution.MultiSigKey, ourContribution.CsvDelay,
deliveryScript, ourDustLimit, msg.ConfirmationDepth)
if err := f.cfg.SendToPeer(fmsg.peerAddress.IdentityKey, fundingResp); err != nil {
fndgLog.Errorf("unable to send funding response to peer: %v", err) fndgLog.Errorf("unable to send funding response to peer: %v", err)
cancelReservation() cancelReservation()
return return
} }
} }
// processFundingRequest sends a message to the fundingManager allowing it to // processFundingAccept sends a message to the fundingManager allowing it to
// continue the second phase of a funding workflow with the target peer. // continue the second phase of a funding workflow with the target peer.
func (f *fundingManager) processFundingResponse(msg *lnwire.SingleFundingResponse, func (f *fundingManager) processFundingResponse(msg *lnwire.AcceptChannel,
peerAddress *lnwire.NetAddress) { peerAddress *lnwire.NetAddress) {
f.fundingMsgs <- &fundingResponseMsg{msg, peerAddress}
f.fundingMsgs <- &fundingAcceptMsg{msg, peerAddress}
} }
// handleFundingResponse processes a response to the workflow initiation sent // handleFundingAceept processes a response to the workflow initiation sent by
// by the remote peer. This message then queues a message with the funding // the remote peer. This message then queues a message with the funding
// outpoint, and a commitment signature to the remote peer. // outpoint, and a commitment signature to the remote peer.
func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) { func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) {
msg := fmsg.msg msg := fmsg.msg
pendingChanID := fmsg.msg.PendingChannelID pendingChanID := fmsg.msg.PendingChannelID
peerKey := fmsg.peerAddress.IdentityKey peerKey := fmsg.peerAddress.IdentityKey
@@ -649,32 +715,40 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) {
} }
} }
// TODO(roasbeeef): perform sanity checks on all the params and
// constraints
fndgLog.Infof("Recv'd fundingResponse for pendingID(%x)", pendingChanID) fndgLog.Infof("Recv'd fundingResponse for pendingID(%x)", pendingChanID)
resCtx.reservation.SetTheirDustLimit(msg.DustLimit) // We'll also specify the responder's preference for the number of
// required confirmations, and also the CSV delay that they specify for
// us within the reservation itself.
resCtx.reservation.SetNumConfsRequired(uint16(msg.MinAcceptDepth))
resCtx.reservation.RequireLocalDelay(uint16(msg.CsvDelay))
// The remote node has responded with their portion of the channel // The remote node has responded with their portion of the channel
// contribution. At this point, we can process their contribution which // contribution. At this point, we can process their contribution which
// allows us to construct and sign both the commitment transaction, and // allows us to construct and sign both the commitment transaction, and
// the funding transaction. // the funding transaction.
_, addrs, _, err := txscript.ExtractPkScriptAddrs(msg.DeliveryPkScript, remoteContribution := &lnwallet.ChannelContribution{
activeNetParams.Params) FirstCommitmentPoint: msg.FirstCommitmentPoint,
if err != nil { ChannelConfig: &channeldb.ChannelConfig{
fndgLog.Errorf("Unable to extract addresses from script: %v", err) ChannelConstraints: channeldb.ChannelConstraints{
cancelReservation()
resCtx.err <- err
return
}
contribution := &lnwallet.ChannelContribution{
FundingAmount: 0,
MultiSigKey: copyPubKey(msg.ChannelDerivationPoint),
CommitKey: copyPubKey(msg.CommitmentKey),
DeliveryAddress: addrs[0],
RevocationKey: copyPubKey(msg.RevocationKey),
DustLimit: msg.DustLimit, DustLimit: msg.DustLimit,
CsvDelay: msg.CsvDelay, MaxPendingAmount: msg.MaxValueInFlight,
ChanReserve: msg.ChannelReserve,
MinHTLC: btcutil.Amount(msg.HtlcMinimum),
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs,
},
MultiSigKey: copyPubKey(msg.FundingKey),
RevocationBasePoint: copyPubKey(msg.RevocationPoint),
PaymentBasePoint: copyPubKey(msg.PaymentPoint),
DelayBasePoint: copyPubKey(msg.DelayedPaymentPoint),
},
} }
if err := resCtx.reservation.ProcessContribution(contribution); err != nil { remoteContribution.CsvDelay = f.cfg.RequiredRemoteDelay(resCtx.chanAmt)
err = resCtx.reservation.ProcessContribution(remoteContribution)
if err != nil {
fndgLog.Errorf("Unable to process contribution from %v: %v", fndgLog.Errorf("Unable to process contribution from %v: %v",
fmsg.peerAddress.IdentityKey, err) fmsg.peerAddress.IdentityKey, err)
cancelReservation() cancelReservation()
@@ -682,6 +756,9 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) {
return return
} }
fndgLog.Infof("pendingChan(%x): remote party proposes num_confs=%v, "+
"csv_delay=%v", pendingChanID, msg.MinAcceptDepth, msg.CsvDelay)
// Now that we have their contribution, we can extract, then send over // Now that we have their contribution, we can extract, then send over
// both the funding out point and our signature for their version of // both the funding out point and our signature for their version of
// the commitment transaction to the remote peer. // the commitment transaction to the remote peer.
@@ -705,16 +782,23 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) {
f.newChanBarriers[channelID] = make(chan struct{}) f.newChanBarriers[channelID] = make(chan struct{})
f.barrierMtx.Unlock() f.barrierMtx.Unlock()
// The next message that advances the funding flow will reference the
// channel via its permanent channel ID, so we'll set up this mapping
// so we can retrieve the reservation context once we get the
// FundingSigned message.
f.resMtx.Lock()
f.signedReservations[channelID] = pendingChanID
f.resMtx.Unlock()
fndgLog.Infof("Generated ChannelPoint(%v) for pendingID(%x)", outPoint, fndgLog.Infof("Generated ChannelPoint(%v) for pendingID(%x)", outPoint,
pendingChanID) pendingChanID)
revocationKey := resCtx.reservation.OurContribution().RevocationKey fundingCreated := &lnwire.FundingCreated{
obsfucator := resCtx.reservation.StateNumObfuscator() PendingChannelID: pendingChanID,
FundingPoint: *outPoint,
fundingComplete := lnwire.NewSingleFundingComplete(pendingChanID, *outPoint, CommitSig: commitSig,
commitSig, revocationKey, obsfucator) }
err = f.cfg.SendToPeer(fmsg.peerAddress.IdentityKey, fundingCreated)
err = f.cfg.SendToPeer(fmsg.peerAddress.IdentityKey, fundingComplete)
if err != nil { if err != nil {
fndgLog.Errorf("Unable to send funding complete message: %v", err) fndgLog.Errorf("Unable to send funding complete message: %v", err)
cancelReservation() cancelReservation()
@@ -723,18 +807,19 @@ func (f *fundingManager) handleFundingResponse(fmsg *fundingResponseMsg) {
} }
} }
// processFundingComplete queues a funding complete message coupled with the // processFundingCreated queues a funding complete message coupled with the
// source peer to the fundingManager. // source peer to the fundingManager.
func (f *fundingManager) processFundingComplete(msg *lnwire.SingleFundingComplete, func (f *fundingManager) processFundingCreated(msg *lnwire.FundingCreated,
peerAddress *lnwire.NetAddress) { peerAddress *lnwire.NetAddress) {
f.fundingMsgs <- &fundingCompleteMsg{msg, peerAddress}
f.fundingMsgs <- &fundingCreatedMsg{msg, peerAddress}
} }
// handleFundingComplete progresses the funding workflow when the daemon is on // handleFundingCreated progresses the funding workflow when the daemon is on
// the responding side of a single funder workflow. Once this message has been // the responding side of a single funder workflow. Once this message has been
// processed, a signature is sent to the remote peer allowing it to broadcast // processed, a signature is sent to the remote peer allowing it to broadcast
// the funding transaction, progressing the workflow into the final stage. // the funding transaction, progressing the workflow into the final stage.
func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) { func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) {
peerKey := fmsg.peerAddress.IdentityKey peerKey := fmsg.peerAddress.IdentityKey
pendingChanID := fmsg.msg.PendingChannelID pendingChanID := fmsg.msg.PendingChannelID
@@ -757,19 +842,16 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
// the commitment transaction. So at this point, we can validate the // the commitment transaction. So at this point, we can validate the
// initiator's commitment transaction, then send our own if it's valid. // initiator's commitment transaction, then send our own if it's valid.
// TODO(roasbeef): make case (p vs P) consistent throughout // TODO(roasbeef): make case (p vs P) consistent throughout
fundingOut := fmsg.msg.FundingOutPoint fundingOut := fmsg.msg.FundingPoint
fndgLog.Infof("completing pendingID(%x) with ChannelPoint(%v)", fndgLog.Infof("completing pendingID(%x) with ChannelPoint(%v)",
pendingChanID, fundingOut) pendingChanID, fundingOut)
revokeKey := copyPubKey(fmsg.msg.RevocationKey)
obsfucator := fmsg.msg.StateHintObsfucator
commitSig := fmsg.msg.CommitSignature.Serialize()
// With all the necessary data available, attempt to advance the // With all the necessary data available, attempt to advance the
// funding workflow to the next stage. If this succeeds then the // funding workflow to the next stage. If this succeeds then the
// funding transaction will broadcast after our next message. // funding transaction will broadcast after our next message.
commitSig := fmsg.msg.CommitSig.Serialize()
completeChan, err := resCtx.reservation.CompleteReservationSingle( completeChan, err := resCtx.reservation.CompleteReservationSingle(
revokeKey, &fundingOut, commitSig, obsfucator) &fundingOut, commitSig)
if err != nil { if err != nil {
// TODO(roasbeef): better error logging: peerID, channelID, etc. // TODO(roasbeef): better error logging: peerID, channelID, etc.
fndgLog.Errorf("unable to complete single reservation: %v", err) fndgLog.Errorf("unable to complete single reservation: %v", err)
@@ -777,18 +859,6 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
return return
} }
// With their signature for our version of the commitment transaction
// verified, we can now send over our signature to the remote peer.
// TODO(roasbeef): just have raw bytes in wire msg? avoids decoding
// then decoding shortly afterwards.
_, sig := resCtx.reservation.OurSignatures()
ourCommitSig, err := btcec.ParseSignature(sig, btcec.S256())
if err != nil {
fndgLog.Errorf("unable to parse signature: %v", err)
cancelReservation()
return
}
// A new channel has almost finished the funding process. In order to // A new channel has almost finished the funding process. In order to
// properly synchronize with the writeHandler goroutine, we add a new // properly synchronize with the writeHandler goroutine, we add a new
// channel to the barriers map which will be closed once the channel is // channel to the barriers map which will be closed once the channel is
@@ -802,9 +872,25 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
fndgLog.Infof("sending signComplete for pendingID(%x) over ChannelPoint(%v)", fndgLog.Infof("sending signComplete for pendingID(%x) over ChannelPoint(%v)",
pendingChanID, fundingOut) pendingChanID, fundingOut)
signComplete := lnwire.NewSingleFundingSignComplete(pendingChanID, ourCommitSig) // With their signature for our version of the commitment transaction
if err := f.cfg.SendToPeer(peerKey, signComplete); err != nil { // verified, we can now send over our signature to the remote peer.
fndgLog.Errorf("unable to send signComplete message: %v", err) //
// TODO(roasbeef): just have raw bytes in wire msg? avoids decoding
// then decoding shortly afterwards.
_, sig := resCtx.reservation.OurSignatures()
ourCommitSig, err := btcec.ParseSignature(sig, btcec.S256())
if err != nil {
fndgLog.Errorf("unable to parse signature: %v", err)
cancelReservation()
return
}
fundingSigned := &lnwire.FundingSigned{
ChanID: channelID,
CommitSig: ourCommitSig,
}
if err := f.cfg.SendToPeer(peerKey, fundingSigned); err != nil {
fndgLog.Errorf("unable to send FundingSigned message: %v", err)
cancelReservation() cancelReservation()
return return
} }
@@ -816,6 +902,9 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
f.localDiscoverySignals[channelID] = make(chan struct{}) f.localDiscoverySignals[channelID] = make(chan struct{})
f.localDiscoveryMtx.Unlock() f.localDiscoveryMtx.Unlock()
// With this last message, our job as the responder is now complete.
// We'll wait for the funding transaction to reach the specified number
// of confirmations, then start normal operations.
go func() { go func() {
doneChan := make(chan struct{}) doneChan := make(chan struct{})
go f.waitForFundingConfirmation(completeChan, doneChan) go f.waitForFundingConfirmation(completeChan, doneChan)
@@ -825,26 +914,39 @@ func (f *fundingManager) handleFundingComplete(fmsg *fundingCompleteMsg) {
}() }()
} }
// processFundingSignComplete sends a single funding sign complete message // processFundingSigned sends a single funding sign complete message along with
// along with the source peer to the funding manager. // the source peer to the funding manager.
func (f *fundingManager) processFundingSignComplete(msg *lnwire.SingleFundingSignComplete, func (f *fundingManager) processFundingSigned(msg *lnwire.FundingSigned,
peerAddress *lnwire.NetAddress) { peerAddress *lnwire.NetAddress) {
f.fundingMsgs <- &fundingSignCompleteMsg{msg, peerAddress}
f.fundingMsgs <- &fundingSignedMsg{msg, peerAddress}
} }
// handleFundingSignComplete processes the final message received in a single // handleFundingSigned processes the final message received in a single funder
// funder workflow. Once this message is processed, the funding transaction is // workflow. Once this message is processed, the funding transaction is
// broadcast. Once the funding transaction reaches a sufficient number of // broadcast. Once the funding transaction reaches a sufficient number of
// confirmations, a message is sent to the responding peer along with a compact // confirmations, a message is sent to the responding peer along with a compact
// encoding of the location of the channel within the blockchain. // encoding of the location of the channel within the blockchain.
func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg) { func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) {
chanID := fmsg.msg.PendingChannelID // As the funding signed message will reference the reservation by it's
peerKey := fmsg.peerAddress.IdentityKey // permanent channel ID, we'll need to perform an intermediate look up
// before we can obtain the reservation.
f.resMtx.Lock()
pendingChanID, ok := f.signedReservations[fmsg.msg.ChanID]
delete(f.signedReservations, fmsg.msg.ChanID)
f.resMtx.Unlock()
if !ok {
fndgLog.Warnf("Unable to find signed reservation for chan_id=%x",
fmsg.msg.ChanID)
return
}
resCtx, err := f.getReservationCtx(peerKey, chanID) peerKey := fmsg.peerAddress.IdentityKey
resCtx, err := f.getReservationCtx(fmsg.peerAddress.IdentityKey,
pendingChanID)
if err != nil { if err != nil {
fndgLog.Warnf("can't find reservation (peerID:%v, chanID:%v)", fndgLog.Warnf("Unable to find reservation (peerID:%v, chanID:%v)",
peerKey, chanID) peerKey, pendingChanID)
return return
} }
@@ -860,20 +962,21 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg)
// The remote peer has responded with a signature for our commitment // The remote peer has responded with a signature for our commitment
// transaction. We'll verify the signature for validity, then commit // transaction. We'll verify the signature for validity, then commit
// the state to disk as we can now open the channel. // the state to disk as we can now open the channel.
commitSig := fmsg.msg.CommitSignature.Serialize() commitSig := fmsg.msg.CommitSig.Serialize()
completeChan, err := resCtx.reservation.CompleteReservation(nil, commitSig) completeChan, err := resCtx.reservation.CompleteReservation(nil, commitSig)
if err != nil { if err != nil {
fndgLog.Errorf("unable to complete reservation sign complete: %v", err) fndgLog.Errorf("Unable to complete reservation sign complete: %v", err)
resCtx.err <- err resCtx.err <- err
if _, err := f.cancelReservationCtx(peerKey, chanID); err != nil { _, err := f.cancelReservationCtx(peerKey, pendingChanID)
fndgLog.Errorf("unable to cancel reservation: %v", err) if err != nil {
fndgLog.Errorf("Unable to cancel reservation: %v", err)
} }
return return
} }
fndgLog.Infof("Finalizing pendingID(%x) over ChannelPoint(%v), "+ fndgLog.Infof("Finalizing pendingID(%x) over ChannelPoint(%v), "+
"waiting for channel open on-chain", chanID, fundingPoint) "waiting for channel open on-chain", pendingChanID, fundingPoint)
// Send an update to the upstream client that the negotiation process // Send an update to the upstream client that the negotiation process
// is over. // is over.
@@ -912,7 +1015,7 @@ func (f *fundingManager) handleFundingSignComplete(fmsg *fundingSignCompleteMsg)
}, },
} }
f.deleteReservationCtx(peerKey, fmsg.msg.PendingChannelID) f.deleteReservationCtx(peerKey, pendingChanID)
}() }()
} }
@@ -951,7 +1054,7 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
return return
} }
fundingPoint := *completeChan.FundingOutpoint fundingPoint := completeChan.FundingOutpoint
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint)
fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)", fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)",
@@ -969,14 +1072,16 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
// Now that the channel has been fully confirmed, we'll mark it as open // Now that the channel has been fully confirmed, we'll mark it as open
// within the database. // within the database.
completeChan.IsPending = false completeChan.IsPending = false
err = f.cfg.Wallet.ChannelDB.MarkChannelAsOpen(&fundingPoint, err = f.cfg.Wallet.Cfg.Database.MarkChannelAsOpen(&fundingPoint, shortChanID)
shortChanID)
if err != nil { if err != nil {
fndgLog.Errorf("error setting channel pending flag to false: "+ fndgLog.Errorf("error setting channel pending flag to false: "+
"%v", err) "%v", err)
return return
} }
// TODO(roasbeef): ideally persistent state update for chan above
// should be abstracted
// With the channel marked open, we'll create the state-machine object // With the channel marked open, we'll create the state-machine object
// which wraps the database state. // which wraps the database state.
channel, err := lnwallet.NewLightningChannel(nil, nil, channel, err := lnwallet.NewLightningChannel(nil, nil,
@@ -1019,16 +1124,16 @@ func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.Open
return return
} }
// processFundingLocked sends a message to the fundingManager allowing it to finish // processFundingLocked sends a message to the fundingManager allowing it to
// the funding workflow. // finish the funding workflow.
func (f *fundingManager) processFundingLocked(msg *lnwire.FundingLocked, func (f *fundingManager) processFundingLocked(msg *lnwire.FundingLocked,
peerAddress *lnwire.NetAddress) { peerAddress *lnwire.NetAddress) {
f.fundingMsgs <- &fundingLockedMsg{msg, peerAddress} f.fundingMsgs <- &fundingLockedMsg{msg, peerAddress}
} }
// handleFundingLocked finalizes the channel funding process and enables the channel // handleFundingLocked finalizes the channel funding process and enables the
// to enter normal operating mode. // channel to enter normal operating mode.
func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) { func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) {
f.localDiscoveryMtx.Lock() f.localDiscoveryMtx.Lock()
localDiscoverySignal, ok := f.localDiscoverySignals[fmsg.msg.ChanID] localDiscoverySignal, ok := f.localDiscoverySignals[fmsg.msg.ChanID]
@@ -1042,8 +1147,8 @@ func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) {
// required to handle forwarded HTLC's. // required to handle forwarded HTLC's.
<-localDiscoverySignal <-localDiscoverySignal
// With the signal received, we can now safely delete the entry from // With the signal received, we can now safely delete the entry
// the map. // from the map.
f.localDiscoveryMtx.Lock() f.localDiscoveryMtx.Lock()
delete(f.localDiscoverySignals, fmsg.msg.ChanID) delete(f.localDiscoverySignals, fmsg.msg.ChanID)
f.localDiscoveryMtx.Unlock() f.localDiscoveryMtx.Unlock()
@@ -1060,10 +1165,19 @@ func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) {
return return
} }
// The funding locked message contains the next commitment point we'll
// need to create the next commitment state for the remote party. So
// we'll insert that into the channel now before passing it along to
// other sub-systems.
err = channel.InitNextRevocation(fmsg.msg.NextPerCommitmentPoint)
if err != nil {
fndgLog.Errorf("unable to insert next commitment point: %v", err)
return
}
// With the channel retrieved, we'll send the breach arbiter the new // With the channel retrieved, we'll send the breach arbiter the new
// channel so it can watch for attempts to breach the channel's // channel so it can watch for attempts to breach the channel's
// contract by the remote // contract by the remote party.
// party.
f.cfg.ArbiterChan <- channel f.cfg.ArbiterChan <- channel
// Launch a defer so we _ensure_ that the channel barrier is properly // Launch a defer so we _ensure_ that the channel barrier is properly
@@ -1248,6 +1362,9 @@ func (f *fundingManager) announceChannel(localIDKey, remoteIDKey, localFundingKe
return return
} }
// TODO(roasbeef): add flag that indicates if should be announced or
// not
f.cfg.SendAnnouncement(ann.chanAnn) f.cfg.SendAnnouncement(ann.chanAnn)
f.cfg.SendAnnouncement(ann.chanUpdateAnn) f.cfg.SendAnnouncement(ann.chanUpdateAnn)
f.cfg.SendAnnouncement(ann.chanProof) f.cfg.SendAnnouncement(ann.chanProof)
@@ -1258,6 +1375,7 @@ func (f *fundingManager) announceChannel(localIDKey, remoteIDKey, localFundingKe
// TODO(roasbeef): re-visit blocking nature.. // TODO(roasbeef): re-visit blocking nature..
func (f *fundingManager) initFundingWorkflow(peerAddress *lnwire.NetAddress, func (f *fundingManager) initFundingWorkflow(peerAddress *lnwire.NetAddress,
req *openChanReq) { req *openChanReq) {
f.fundingRequests <- &initFundingMsg{ f.fundingRequests <- &initFundingMsg{
peerAddress: peerAddress, peerAddress: peerAddress,
openChanReq: req, openChanReq: req,
@@ -1274,19 +1392,19 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
localAmt = msg.localFundingAmt localAmt = msg.localFundingAmt
remoteAmt = msg.remoteFundingAmt remoteAmt = msg.remoteFundingAmt
capacity = localAmt + remoteAmt capacity = localAmt + remoteAmt
numConfs = msg.numConfs
ourDustLimit = lnwallet.DefaultDustLimit() ourDustLimit = lnwallet.DefaultDustLimit()
) )
fndgLog.Infof("Initiating fundingRequest(localAmt=%v, remoteAmt=%v, "+ fndgLog.Infof("Initiating fundingRequest(localAmt=%v, remoteAmt=%v, "+
"capacity=%v, numConfs=%v, addr=%v, dustLimit=%v)", localAmt, "capacity=%v, chainhash=%v, addr=%v, dustLimit=%v)", localAmt,
msg.pushAmt, capacity, numConfs, msg.peerAddress.Address, msg.pushAmt, capacity, msg.chainHash, msg.peerAddress.Address,
ourDustLimit) ourDustLimit)
// First, we'll query the fee estimator for a fee that should get the // First, we'll query the fee estimator for a fee that should get the
// commitment transaction into the next block (conf target of 1). We // commitment transaction into the next block (conf target of 1). We
// target the next block here to ensure that we'll be able to execute a // target the next block here to ensure that we'll be able to execute a
// timely unilateral channel closure if needed. // timely unilateral channel closure if needed.
//
// TODO(roasbeef): shouldn't be targeting next block // TODO(roasbeef): shouldn't be targeting next block
feePerWeight := btcutil.Amount(f.cfg.FeeEstimator.EstimateFeePerWeight(1)) feePerWeight := btcutil.Amount(f.cfg.FeeEstimator.EstimateFeePerWeight(1))
@@ -1295,11 +1413,11 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
feePerKw := feePerWeight * 1000 feePerKw := feePerWeight * 1000
// Initialize a funding reservation with the local wallet. If the // Initialize a funding reservation with the local wallet. If the
// wallet doesn't have enough funds to commit to this channel, then // wallet doesn't have enough funds to commit to this channel, then the
// the request will fail, and be aborted. // request will fail, and be aborted.
reservation, err := f.cfg.Wallet.InitChannelReservation(capacity, reservation, err := f.cfg.Wallet.InitChannelReservation(capacity,
localAmt, peerKey, msg.peerAddress.Address, uint16(numConfs), 4, localAmt, msg.pushAmt, feePerKw, peerKey,
ourDustLimit, msg.pushAmt, feePerKw) msg.peerAddress.Address, &msg.chainHash)
if err != nil { if err != nil {
msg.err <- err msg.err <- err
return return
@@ -1322,6 +1440,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
} }
f.activeReservations[peerIDKey][chanID] = &reservationWithCtx{ f.activeReservations[peerIDKey][chanID] = &reservationWithCtx{
chanAmt: capacity,
reservation: reservation, reservation: reservation,
peerAddress: msg.peerAddress, peerAddress: msg.peerAddress,
updates: msg.updates, updates: msg.updates,
@@ -1329,35 +1448,38 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
} }
f.resMtx.Unlock() f.resMtx.Unlock()
// Using the RequiredRemoteDelay closure, we'll compute the remote CSV
// delay we require given the total amount of funds within the channel.
remoteCsvDelay := f.cfg.RequiredRemoteDelay(capacity)
// TODO(roasbeef): require remote delay?
// Once the reservation has been created, and indexed, queue a funding // Once the reservation has been created, and indexed, queue a funding
// request to the remote peer, kicking off the funding workflow. // request to the remote peer, kicking off the funding workflow.
contribution := reservation.OurContribution() ourContribution := reservation.OurContribution()
deliveryScript, err := txscript.PayToAddrScript(contribution.DeliveryAddress)
if err != nil {
fndgLog.Errorf("Unable to convert address to pkscript: %v", err)
msg.err <- err
return
}
fndgLog.Infof("Starting funding workflow with %v for pendingID(%x)", fndgLog.Infof("Starting funding workflow with %v for pendingID(%x)",
msg.peerAddress.Address, chanID) msg.peerAddress.Address, chanID)
// TODO(roasbeef): add FundingRequestFromContribution func fundingOpen := lnwire.OpenChannel{
fundingReq := lnwire.NewSingleFundingRequest( ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash,
chanID, PendingChannelID: chanID,
msg.channelType, FundingAmount: capacity,
msg.coinType, PushAmount: msg.pushAmt,
feePerKw, DustLimit: ourContribution.DustLimit,
capacity, MaxValueInFlight: ourContribution.MaxPendingAmount,
contribution.CsvDelay, ChannelReserve: ourContribution.ChanReserve,
contribution.CommitKey, HtlcMinimum: uint32(ourContribution.MinHTLC),
contribution.MultiSigKey, FeePerKiloWeight: uint32(feePerKw),
deliveryScript, CsvDelay: uint16(remoteCsvDelay),
ourDustLimit, MaxAcceptedHTLCs: ourContribution.MaxAcceptedHtlcs,
msg.pushAmt, FundingKey: ourContribution.MultiSigKey,
numConfs, RevocationPoint: ourContribution.RevocationBasePoint,
) PaymentPoint: ourContribution.PaymentBasePoint,
if err := f.cfg.SendToPeer(peerKey, fundingReq); err != nil { DelayedPaymentPoint: ourContribution.DelayBasePoint,
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint,
}
if err := f.cfg.SendToPeer(peerKey, &fundingOpen); err != nil {
fndgLog.Errorf("Unable to send funding request message: %v", err) fndgLog.Errorf("Unable to send funding request message: %v", err)
msg.err <- err msg.err <- err
return return
@@ -1429,49 +1551,52 @@ func (f *fundingManager) handleErrorMsg(fmsg *fundingErrorMsg) {
// cancelReservationCtx do all needed work in order to securely cancel the // cancelReservationCtx do all needed work in order to securely cancel the
// reservation. // reservation.
func (f *fundingManager) cancelReservationCtx(peerKey *btcec.PublicKey, func (f *fundingManager) cancelReservationCtx(peerKey *btcec.PublicKey,
chanID [32]byte) (*reservationWithCtx, error) { pendingChanID [32]byte) (*reservationWithCtx, error) {
fndgLog.Infof("Cancelling funding reservation for node_key=%x, "+ fndgLog.Infof("Cancelling funding reservation for node_key=%x, "+
"chan_id=%x", peerKey.SerializeCompressed(), chanID) "chan_id=%x", peerKey.SerializeCompressed(), pendingChanID)
ctx, err := f.getReservationCtx(peerKey, chanID) ctx, err := f.getReservationCtx(peerKey, pendingChanID)
if err != nil { if err != nil {
return nil, errors.Errorf("can't find reservation: %v", return nil, errors.Errorf("unable to find reservation: %v",
err) err)
} }
if err := ctx.reservation.Cancel(); err != nil { if err := ctx.reservation.Cancel(); err != nil {
return nil, errors.Errorf("can't cancel reservation: %v", return nil, errors.Errorf("unable to cancel reservation: %v",
err) err)
} }
f.deleteReservationCtx(peerKey, chanID) f.deleteReservationCtx(peerKey, pendingChanID)
return ctx, nil return ctx, nil
} }
// deleteReservationCtx is needed in order to securely delete the reservation. // deleteReservationCtx deletes the reservation uniquely identified by the
// target public key of the peer, and the specified pending channel ID.
func (f *fundingManager) deleteReservationCtx(peerKey *btcec.PublicKey, func (f *fundingManager) deleteReservationCtx(peerKey *btcec.PublicKey,
chanID [32]byte) { pendingChanID [32]byte) {
// TODO(roasbeef): possibly cancel funding barrier in peer's // TODO(roasbeef): possibly cancel funding barrier in peer's
// channelManager? // channelManager?
peerIDKey := newSerializedKey(peerKey) peerIDKey := newSerializedKey(peerKey)
f.resMtx.Lock() f.resMtx.Lock()
delete(f.activeReservations[peerIDKey], chanID) delete(f.activeReservations[peerIDKey], pendingChanID)
f.resMtx.Unlock() f.resMtx.Unlock()
} }
// getReservationCtx returns the reservation context by peer id and channel id. // getReservationCtx returns the reservation context for a particular pending
// channel ID for a target peer.
func (f *fundingManager) getReservationCtx(peerKey *btcec.PublicKey, func (f *fundingManager) getReservationCtx(peerKey *btcec.PublicKey,
chanID [32]byte) (*reservationWithCtx, error) { pendingChanID [32]byte) (*reservationWithCtx, error) {
peerIDKey := newSerializedKey(peerKey) peerIDKey := newSerializedKey(peerKey)
f.resMtx.RLock() f.resMtx.RLock()
resCtx, ok := f.activeReservations[peerIDKey][chanID] resCtx, ok := f.activeReservations[peerIDKey][pendingChanID]
f.resMtx.RUnlock() f.resMtx.RUnlock()
if !ok { if !ok {
return nil, errors.Errorf("unknown channel (id: %v)", chanID) return nil, errors.Errorf("unknown channel (id: %v)",
pendingChanID)
} }
return resCtx, nil return resCtx, nil
+8 -10
View File
@@ -20,6 +20,7 @@ import (
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/btcec"
"github.com/roasbeef/btcd/chaincfg/chainhash"
"github.com/roasbeef/btcd/connmgr" "github.com/roasbeef/btcd/connmgr"
"github.com/roasbeef/btcutil" "github.com/roasbeef/btcutil"
@@ -952,22 +953,20 @@ type disconnectPeerMsg struct {
} }
// openChanReq is a message sent to the server in order to request the // openChanReq is a message sent to the server in order to request the
// initiation of a channel funding workflow to the peer with either the specified // initiation of a channel funding workflow to the peer with either the
// relative peer ID, or a global lightning ID. // specified relative peer ID, or a global lightning ID.
type openChanReq struct { type openChanReq struct {
targetPeerID int32 targetPeerID int32
targetPubkey *btcec.PublicKey targetPubkey *btcec.PublicKey
// TODO(roasbeef): make enums in lnwire chainHash chainhash.Hash
channelType uint8
coinType uint64
localFundingAmt btcutil.Amount localFundingAmt btcutil.Amount
remoteFundingAmt btcutil.Amount remoteFundingAmt btcutil.Amount
pushAmt btcutil.Amount pushAmt btcutil.Amount
numConfs uint32 // TODO(roasbeef): add ability to specify channel constraints as well
updates chan *lnrpc.OpenStatusUpdate updates chan *lnrpc.OpenStatusUpdate
err chan error err chan error
@@ -1247,10 +1246,9 @@ func (s *server) DisconnectPeer(pubKey *btcec.PublicKey) error {
} }
// OpenChannel sends a request to the server to open a channel to the specified // OpenChannel sends a request to the server to open a channel to the specified
// peer identified by ID with the passed channel funding paramters. // peer identified by ID with the passed channel funding parameters.
func (s *server) OpenChannel(peerID int32, nodeKey *btcec.PublicKey, func (s *server) OpenChannel(peerID int32, nodeKey *btcec.PublicKey,
localAmt, pushAmt btcutil.Amount, localAmt, pushAmt btcutil.Amount) (chan *lnrpc.OpenStatusUpdate, chan error) {
numConfs uint32) (chan *lnrpc.OpenStatusUpdate, chan error) {
errChan := make(chan error, 1) errChan := make(chan error, 1)
updateChan := make(chan *lnrpc.OpenStatusUpdate, 1) updateChan := make(chan *lnrpc.OpenStatusUpdate, 1)
@@ -1258,9 +1256,9 @@ func (s *server) OpenChannel(peerID int32, nodeKey *btcec.PublicKey,
req := &openChanReq{ req := &openChanReq{
targetPeerID: peerID, targetPeerID: peerID,
targetPubkey: nodeKey, targetPubkey: nodeKey,
chainHash: *activeNetParams.GenesisHash,
localFundingAmt: localAmt, localFundingAmt: localAmt,
pushAmt: pushAmt, pushAmt: pushAmt,
numConfs: numConfs,
updates: updateChan, updates: updateChan,
err: errChan, err: errChan,
} }