You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3167 lines
107 KiB
3167 lines
107 KiB
package lnd |
|
|
|
import ( |
|
"bytes" |
|
"encoding/binary" |
|
"fmt" |
|
"sync" |
|
"sync/atomic" |
|
"time" |
|
|
|
"github.com/btcsuite/btcd/btcec" |
|
"github.com/btcsuite/btcd/chaincfg/chainhash" |
|
"github.com/btcsuite/btcd/wire" |
|
"github.com/btcsuite/btcutil" |
|
"github.com/coreos/bbolt" |
|
"github.com/davecgh/go-spew/spew" |
|
"github.com/go-errors/errors" |
|
"github.com/lightningnetwork/lnd/chainntnfs" |
|
"github.com/lightningnetwork/lnd/channeldb" |
|
"github.com/lightningnetwork/lnd/discovery" |
|
"github.com/lightningnetwork/lnd/htlcswitch" |
|
"github.com/lightningnetwork/lnd/input" |
|
"github.com/lightningnetwork/lnd/keychain" |
|
"github.com/lightningnetwork/lnd/lnpeer" |
|
"github.com/lightningnetwork/lnd/lnrpc" |
|
"github.com/lightningnetwork/lnd/lnwallet" |
|
"github.com/lightningnetwork/lnd/lnwire" |
|
"github.com/lightningnetwork/lnd/routing" |
|
"golang.org/x/crypto/salsa20" |
|
"google.golang.org/grpc" |
|
) |
|
|
|
const ( |
|
// TODO(roasbeef): tune |
|
msgBufferSize = 50 |
|
|
|
// minBtcRemoteDelay and maxBtcRemoteDelay is the extremes of the |
|
// Bitcoin CSV delay we will require the remote to use for its |
|
// commitment transaction. The actual delay we will require will be |
|
// somewhere between these values, depending on channel size. |
|
minBtcRemoteDelay uint16 = 144 |
|
maxBtcRemoteDelay uint16 = 2016 |
|
|
|
// minLtcRemoteDelay and maxLtcRemoteDelay is the extremes of the |
|
// Litecoin CSV delay we will require the remote to use for its |
|
// commitment transaction. The actual delay we will require will be |
|
// somewhere between these values, depending on channel size. |
|
minLtcRemoteDelay uint16 = 576 |
|
maxLtcRemoteDelay uint16 = 8064 |
|
|
|
// maxWaitNumBlocksFundingConf is the maximum number of blocks to wait |
|
// for the funding transaction to be confirmed before forgetting about |
|
// the channel. 288 blocks is ~48 hrs |
|
maxWaitNumBlocksFundingConf = 288 |
|
|
|
// minChanFundingSize is the smallest channel that we'll allow to be |
|
// created over the RPC interface. |
|
minChanFundingSize = btcutil.Amount(20000) |
|
|
|
// maxBtcFundingAmount is a soft-limit of the maximum channel size |
|
// currently accepted on the Bitcoin chain within the Lightning |
|
// Protocol. This limit is defined in BOLT-0002, and serves as an |
|
// initial precautionary limit while implementations are battle tested |
|
// in the real world. |
|
maxBtcFundingAmount = btcutil.Amount(1<<24) - 1 |
|
|
|
// maxLtcFundingAmount is a soft-limit of the maximum channel size |
|
// currently accepted on the Litecoin chain within the Lightning |
|
// Protocol. |
|
maxLtcFundingAmount = maxBtcFundingAmount * btcToLtcConversionRate |
|
) |
|
|
|
var ( |
|
// maxFundingAmount is a soft-limit of the maximum channel size |
|
// currently accepted within the Lightning Protocol. This limit is |
|
// defined in BOLT-0002, and serves as an initial precautionary limit |
|
// while implementations are battle tested in the real world. |
|
// |
|
// At the moment, this value depends on which chain is active. It is set |
|
// to the value under the Bitcoin chain as default. |
|
// |
|
// TODO(roasbeef): add command line param to modify |
|
maxFundingAmount = maxBtcFundingAmount |
|
|
|
// ErrFundingManagerShuttingDown is an error returned when attempting to |
|
// process a funding request/message but the funding manager has already |
|
// been signaled to shut down. |
|
ErrFundingManagerShuttingDown = errors.New("funding manager shutting " + |
|
"down") |
|
) |
|
|
|
// reservationWithCtx encapsulates a pending channel reservation. This wrapper |
|
// struct is used internally within the funding manager to track and progress |
|
// the funding workflow initiated by incoming/outgoing methods from the target |
|
// 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 |
|
// via a local signal such as RPC. |
|
// |
|
// TODO(roasbeef): actually use the context package |
|
// * deadlines, etc. |
|
type reservationWithCtx struct { |
|
reservation *lnwallet.ChannelReservation |
|
peer lnpeer.Peer |
|
|
|
chanAmt btcutil.Amount |
|
|
|
// Constraints we require for the remote. |
|
remoteCsvDelay uint16 |
|
remoteMinHtlc lnwire.MilliSatoshi |
|
|
|
updateMtx sync.RWMutex |
|
lastUpdated time.Time |
|
|
|
updates chan *lnrpc.OpenStatusUpdate |
|
err chan error |
|
} |
|
|
|
// isLocked checks the reservation's timestamp to determine whether it is locked. |
|
func (r *reservationWithCtx) isLocked() bool { |
|
r.updateMtx.RLock() |
|
defer r.updateMtx.RUnlock() |
|
|
|
// The time zero value represents a locked reservation. |
|
return r.lastUpdated.IsZero() |
|
} |
|
|
|
// lock locks the reservation from zombie pruning by setting its timestamp to the |
|
// zero value. |
|
func (r *reservationWithCtx) lock() { |
|
r.updateMtx.Lock() |
|
defer r.updateMtx.Unlock() |
|
|
|
r.lastUpdated = time.Time{} |
|
} |
|
|
|
// updateTimestamp updates the reservation's timestamp with the current time. |
|
func (r *reservationWithCtx) updateTimestamp() { |
|
r.updateMtx.Lock() |
|
defer r.updateMtx.Unlock() |
|
|
|
r.lastUpdated = time.Now() |
|
} |
|
|
|
// initFundingMsg is sent by an outside subsystem to the funding manager in |
|
// order to kick off a funding workflow with a specified target peer. The |
|
// original request which defines the parameters of the funding workflow are |
|
// embedded within this message giving the funding manager full context w.r.t |
|
// the workflow. |
|
type initFundingMsg struct { |
|
peer lnpeer.Peer |
|
*openChanReq |
|
} |
|
|
|
// fundingOpenMsg couples an lnwire.OpenChannel message with the peer who sent |
|
// the message. This allows the funding manager to queue a response directly to |
|
// the peer, progressing the funding workflow. |
|
type fundingOpenMsg struct { |
|
msg *lnwire.OpenChannel |
|
peer lnpeer.Peer |
|
} |
|
|
|
// fundingAcceptMsg couples an lnwire.AcceptChannel message with the peer who |
|
// sent the message. This allows the funding manager to queue a response |
|
// directly to the peer, progressing the funding workflow. |
|
type fundingAcceptMsg struct { |
|
msg *lnwire.AcceptChannel |
|
peer lnpeer.Peer |
|
} |
|
|
|
// fundingCreatedMsg couples an lnwire.FundingCreated message with the peer who |
|
// sent the message. This allows the funding manager to queue a response |
|
// directly to the peer, progressing the funding workflow. |
|
type fundingCreatedMsg struct { |
|
msg *lnwire.FundingCreated |
|
peer lnpeer.Peer |
|
} |
|
|
|
// fundingSignedMsg couples an lnwire.FundingSigned message with the peer who |
|
// sent the message. This allows the funding manager to queue a response |
|
// directly to the peer, progressing the funding workflow. |
|
type fundingSignedMsg struct { |
|
msg *lnwire.FundingSigned |
|
peer lnpeer.Peer |
|
} |
|
|
|
// fundingLockedMsg couples an lnwire.FundingLocked message with the peer who |
|
// sent the message. This allows the funding manager to finalize the funding |
|
// process and announce the existence of the new channel. |
|
type fundingLockedMsg struct { |
|
msg *lnwire.FundingLocked |
|
peer lnpeer.Peer |
|
} |
|
|
|
// fundingErrorMsg couples an lnwire.Error message with the peer who sent the |
|
// message. This allows the funding manager to properly process the error. |
|
type fundingErrorMsg struct { |
|
err *lnwire.Error |
|
peerKey *btcec.PublicKey |
|
} |
|
|
|
// pendingChannels is a map instantiated per-peer which tracks all active |
|
// 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 |
|
|
|
// serializedPubKey is used within the FundingManager's activeReservations list |
|
// to identify the nodes with which the FundingManager is actively working to |
|
// initiate new channels. |
|
type serializedPubKey [33]byte |
|
|
|
// newSerializedKey creates a new serialized public key from an instance of a |
|
// live pubkey object. |
|
func newSerializedKey(pubKey *btcec.PublicKey) serializedPubKey { |
|
var s serializedPubKey |
|
copy(s[:], pubKey.SerializeCompressed()) |
|
return s |
|
} |
|
|
|
// fundingConfig defines the configuration for the FundingManager. All elements |
|
// within the configuration MUST be non-nil for the FundingManager to carry out |
|
// its duties. |
|
type fundingConfig struct { |
|
// IDKey is the PublicKey that is used to identify this node within the |
|
// Lightning Network. |
|
IDKey *btcec.PublicKey |
|
|
|
// Wallet handles the parts of the funding process that involves moving |
|
// funds from on-chain transaction outputs into Lightning channels. |
|
Wallet *lnwallet.LightningWallet |
|
|
|
// PublishTransaction facilitates the process of broadcasting a |
|
// transaction to the network. |
|
PublishTransaction func(*wire.MsgTx) error |
|
|
|
// FeeEstimator calculates appropriate fee rates based on historical |
|
// transaction information. |
|
FeeEstimator lnwallet.FeeEstimator |
|
|
|
// Notifier is used by the FundingManager to determine when the |
|
// channel's funding transaction has been confirmed on the blockchain |
|
// so that the channel creation process can be completed. |
|
Notifier chainntnfs.ChainNotifier |
|
|
|
// SignMessage signs an arbitrary method with a given public key. 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 |
|
// 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) |
|
|
|
// CurrentNodeAnnouncement should return the latest, fully signed node |
|
// announcement from the backing Lightning Network node. |
|
CurrentNodeAnnouncement func() (lnwire.NodeAnnouncement, error) |
|
|
|
// SendAnnouncement is used by the FundingManager to send announcement |
|
// messages to the Gossiper to possibly broadcast to the greater |
|
// network. A set of optional message fields can be provided to populate |
|
// any information within the graph that is not included in the gossip |
|
// message. |
|
SendAnnouncement func(msg lnwire.Message, |
|
optionalFields ...discovery.OptionalMsgField) chan error |
|
|
|
// NotifyWhenOnline allows the FundingManager to register with a |
|
// subsystem that will notify it when the peer comes online. This is |
|
// used when sending the fundingLocked message, since it MUST be |
|
// delivered after the funding transaction is confirmed. |
|
// |
|
// NOTE: The peerChan channel must be buffered. |
|
NotifyWhenOnline func(peer *btcec.PublicKey, peerChan chan<- lnpeer.Peer) |
|
|
|
// FindChannel queries the database for the channel with the given |
|
// channel ID. |
|
FindChannel func(chanID lnwire.ChannelID) (*channeldb.OpenChannel, error) |
|
|
|
// TempChanIDSeed is a cryptographically random string of bytes that's |
|
// used as a seed to generate pending channel ID's. |
|
TempChanIDSeed [32]byte |
|
|
|
// DefaultRoutingPolicy is the default routing policy used when |
|
// initially announcing channels. |
|
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, lnwire.MilliSatoshi) 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 |
|
|
|
// RequiredRemoteChanReserve is a function closure that, given the |
|
// channel capacity and dust limit, will return an appropriate amount |
|
// for the remote peer's required channel reserve that is to be adhered |
|
// to at all times. |
|
RequiredRemoteChanReserve func(capacity, dustLimit btcutil.Amount) btcutil.Amount |
|
|
|
// RequiredRemoteMaxValue is a function closure that, given the channel |
|
// capacity, returns the amount of MilliSatoshis that our remote peer |
|
// can have in total outstanding HTLCs with us. |
|
RequiredRemoteMaxValue func(btcutil.Amount) lnwire.MilliSatoshi |
|
|
|
// RequiredRemoteMaxHTLCs is a function closure that, given the channel |
|
// capacity, returns the number of maximum HTLCs the remote peer can |
|
// offer us. |
|
RequiredRemoteMaxHTLCs func(btcutil.Amount) uint16 |
|
|
|
// WatchNewChannel is to be called once a new channel enters the final |
|
// funding stage: waiting for on-chain confirmation. This method sends |
|
// the channel to the ChainArbitrator so it can watch for any on-chain |
|
// events related to the channel. We also provide the public key of the |
|
// node we're establishing a channel with for reconnection purposes. |
|
WatchNewChannel func(*channeldb.OpenChannel, *btcec.PublicKey) error |
|
|
|
// ReportShortChanID allows the funding manager to report the newly |
|
// discovered short channel ID of a formerly pending channel to outside |
|
// sub-systems. |
|
ReportShortChanID func(wire.OutPoint) error |
|
|
|
// ZombieSweeperInterval is the periodic time interval in which the |
|
// zombie sweeper is run. |
|
ZombieSweeperInterval time.Duration |
|
|
|
// ReservationTimeout is the length of idle time that must pass before |
|
// a reservation is considered a zombie. |
|
ReservationTimeout time.Duration |
|
|
|
// MinChanSize is the smallest channel size that we'll accept as an |
|
// inbound channel. We have such a parameter, as otherwise, nodes could |
|
// flood us with very small channels that would never really be usable |
|
// due to fees. |
|
MinChanSize btcutil.Amount |
|
|
|
// NotifyOpenChannelEvent informs the ChannelNotifier when channels |
|
// transition from pending open to open. |
|
NotifyOpenChannelEvent func(wire.OutPoint) |
|
} |
|
|
|
// fundingManager acts as an orchestrator/bridge between the wallet's |
|
// 'ChannelReservation' workflow, and the wire protocol's funding initiation |
|
// messages. Any requests to initiate the funding workflow for a channel, |
|
// either kicked-off locally or remotely handled by the funding manager. |
|
// Once a channel's funding workflow has been completed, any local callers, the |
|
// local peer, and possibly the remote peer are notified of the completion of |
|
// the channel workflow. Additionally, any temporary or permanent access |
|
// controls between the wallet and remote peers are enforced via the funding |
|
// manager. |
|
type fundingManager struct { |
|
// MUST be used atomically. |
|
started int32 |
|
stopped int32 |
|
|
|
// cfg is a copy of the configuration struct that the FundingManager |
|
// was initialized with. |
|
cfg *fundingConfig |
|
|
|
// chanIDKey is a cryptographically random key that's used to generate |
|
// temporary channel ID's. |
|
chanIDKey [32]byte |
|
|
|
// chanIDNonce is a nonce that's incremented for each new funding |
|
// reservation created. |
|
nonceMtx sync.RWMutex |
|
chanIDNonce uint64 |
|
|
|
// activeReservations is a map which houses the state of all pending |
|
// funding workflows. |
|
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 safe. |
|
resMtx sync.RWMutex |
|
|
|
// fundingMsgs is a channel which receives wrapped wire messages |
|
// related to funding workflow from outside peers. |
|
fundingMsgs chan interface{} |
|
|
|
// queries is a channel which receives requests to query the internal |
|
// state of the funding manager. |
|
queries chan interface{} |
|
|
|
// fundingRequests is a channel used to receive channel initiation |
|
// requests from a local subsystem within the daemon. |
|
fundingRequests chan *initFundingMsg |
|
|
|
// newChanBarriers is a map from a channel ID to a 'barrier' which will |
|
// be signalled once the channel is fully open. This barrier acts as a |
|
// synchronization point for any incoming/outgoing HTLCs before the |
|
// channel has been fully opened. |
|
barrierMtx sync.RWMutex |
|
newChanBarriers map[lnwire.ChannelID]chan struct{} |
|
|
|
localDiscoveryMtx sync.Mutex |
|
localDiscoverySignals map[lnwire.ChannelID]chan struct{} |
|
|
|
handleFundingLockedMtx sync.RWMutex |
|
handleFundingLockedBarriers map[lnwire.ChannelID]struct{} |
|
|
|
quit chan struct{} |
|
wg sync.WaitGroup |
|
} |
|
|
|
// channelOpeningState represents the different states a channel can be in |
|
// between the funding transaction has been confirmed and the channel is |
|
// announced to the network and ready to be used. |
|
type channelOpeningState uint8 |
|
|
|
const ( |
|
// markedOpen is the opening state of a channel if the funding |
|
// transaction is confirmed on-chain, but fundingLocked is not yet |
|
// successfully sent to the other peer. |
|
markedOpen channelOpeningState = iota |
|
|
|
// fundingLockedSent is the opening state of a channel if the |
|
// fundingLocked message has successfully been sent to the other peer, |
|
// but we still haven't announced the channel to the network. |
|
fundingLockedSent |
|
|
|
// addedToRouterGraph is the opening state of a channel if the |
|
// channel has been successfully added to the router graph |
|
// immediately after the fundingLocked message has been sent, but |
|
// we still haven't announced the channel to the network. |
|
addedToRouterGraph |
|
) |
|
|
|
var ( |
|
// channelOpeningStateBucket is the database bucket used to store the |
|
// channelOpeningState for each channel that is currently in the process |
|
// of being opened. |
|
channelOpeningStateBucket = []byte("channelOpeningState") |
|
|
|
// ErrChannelNotFound is an error returned when a channel is not known |
|
// to us. In this case of the fundingManager, this error is returned |
|
// when the channel in question is not considered being in an opening |
|
// state. |
|
ErrChannelNotFound = fmt.Errorf("channel not found") |
|
) |
|
|
|
// newFundingManager creates and initializes a new instance of the |
|
// fundingManager. |
|
func newFundingManager(cfg fundingConfig) (*fundingManager, error) { |
|
return &fundingManager{ |
|
cfg: &cfg, |
|
chanIDKey: cfg.TempChanIDSeed, |
|
activeReservations: make(map[serializedPubKey]pendingChannels), |
|
signedReservations: make(map[lnwire.ChannelID][32]byte), |
|
newChanBarriers: make(map[lnwire.ChannelID]chan struct{}), |
|
fundingMsgs: make(chan interface{}, msgBufferSize), |
|
fundingRequests: make(chan *initFundingMsg, msgBufferSize), |
|
localDiscoverySignals: make(map[lnwire.ChannelID]chan struct{}), |
|
handleFundingLockedBarriers: make(map[lnwire.ChannelID]struct{}), |
|
queries: make(chan interface{}, 1), |
|
quit: make(chan struct{}), |
|
}, nil |
|
} |
|
|
|
// Start launches all helper goroutines required for handling requests sent |
|
// to the funding manager. |
|
func (f *fundingManager) Start() error { |
|
if atomic.AddInt32(&f.started, 1) != 1 { // TODO(roasbeef): CAS instead |
|
return nil |
|
} |
|
|
|
fndgLog.Tracef("Funding manager running") |
|
|
|
// Upon restart, the Funding Manager will check the database to load any |
|
// channels that were waiting for their funding transactions to be |
|
// confirmed on the blockchain at the time when the daemon last went |
|
// down. |
|
// TODO(roasbeef): store height that funding finished? |
|
// * would then replace call below |
|
pendingChannels, err := f.cfg.Wallet.Cfg.Database.FetchPendingChannels() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
// For any channels that were in a pending state when the daemon was |
|
// last connected, the Funding Manager will re-initialize the channel |
|
// barriers and will also launch waitForFundingConfirmation to wait for |
|
// the channel's funding transaction to be confirmed on the blockchain. |
|
for _, channel := range pendingChannels { |
|
f.barrierMtx.Lock() |
|
fndgLog.Tracef("Loading pending ChannelPoint(%v), creating chan "+ |
|
"barrier", channel.FundingOutpoint) |
|
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) |
|
f.newChanBarriers[chanID] = make(chan struct{}) |
|
f.barrierMtx.Unlock() |
|
|
|
f.localDiscoverySignals[chanID] = make(chan struct{}) |
|
|
|
// Rebroadcast the funding transaction for any pending channel |
|
// that we initiated. If this operation fails due to a reported |
|
// double spend, we treat this as an indicator that we have |
|
// already broadcast this transaction. Otherwise, we simply log |
|
// the error as there isn't anything we can currently do to |
|
// recover. |
|
if channel.ChanType == channeldb.SingleFunder && |
|
channel.IsInitiator { |
|
|
|
err := f.cfg.PublishTransaction(channel.FundingTxn) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to rebroadcast funding "+ |
|
"tx for ChannelPoint(%v): %v", |
|
channel.FundingOutpoint, err) |
|
} |
|
} |
|
|
|
confChan := make(chan *lnwire.ShortChannelID) |
|
timeoutChan := make(chan struct{}) |
|
|
|
go func(ch *channeldb.OpenChannel) { |
|
go f.waitForFundingWithTimeout(ch, confChan, timeoutChan) |
|
|
|
select { |
|
case <-timeoutChan: |
|
// Timeout channel will be triggered if the number of blocks |
|
// mined since the channel was initiated reaches |
|
// maxWaitNumBlocksFundingConf and we are not the channel |
|
// initiator. |
|
localBalance := ch.LocalCommitment.LocalBalance.ToSatoshis() |
|
closeInfo := &channeldb.ChannelCloseSummary{ |
|
ChainHash: ch.ChainHash, |
|
ChanPoint: ch.FundingOutpoint, |
|
RemotePub: ch.IdentityPub, |
|
Capacity: ch.Capacity, |
|
SettledBalance: localBalance, |
|
CloseType: channeldb.FundingCanceled, |
|
RemoteCurrentRevocation: ch.RemoteCurrentRevocation, |
|
RemoteNextRevocation: ch.RemoteNextRevocation, |
|
LocalChanConfig: ch.LocalChanCfg, |
|
} |
|
|
|
if err := ch.CloseChannel(closeInfo); err != nil { |
|
fndgLog.Errorf("Failed closing channel "+ |
|
"%v: %v", ch.FundingOutpoint, err) |
|
} |
|
|
|
case <-f.quit: |
|
// The fundingManager is shutting down, and will |
|
// resume wait on startup. |
|
case shortChanID, ok := <-confChan: |
|
if !ok { |
|
fndgLog.Errorf("Waiting for funding" + |
|
"confirmation failed") |
|
return |
|
} |
|
|
|
// The funding transaction has confirmed, so |
|
// we'll attempt to retrieve the remote peer |
|
// to complete the rest of the funding flow. |
|
peerChan := make(chan lnpeer.Peer, 1) |
|
f.cfg.NotifyWhenOnline(ch.IdentityPub, peerChan) |
|
|
|
var peer lnpeer.Peer |
|
select { |
|
case peer = <-peerChan: |
|
case <-f.quit: |
|
return |
|
} |
|
err := f.handleFundingConfirmation( |
|
peer, ch, shortChanID, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("Failed to handle "+ |
|
"funding confirmation: %v", err) |
|
return |
|
} |
|
} |
|
}(channel) |
|
} |
|
|
|
// Fetch all our open channels, and make sure they all finalized the |
|
// opening process. |
|
// TODO(halseth): this check is only done on restart atm, but should |
|
// also be done if a peer that disappeared during the opening process |
|
// reconnects. |
|
openChannels, err := f.cfg.Wallet.Cfg.Database.FetchAllChannels() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
for _, channel := range openChannels { |
|
channelState, shortChanID, err := f.getChannelOpeningState( |
|
&channel.FundingOutpoint) |
|
if err == ErrChannelNotFound { |
|
// Channel not in fundingManager's opening database, |
|
// meaning it was successfully announced to the |
|
// network. |
|
continue |
|
} else if err != nil { |
|
return err |
|
} |
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) |
|
fndgLog.Debugf("channel (%v) with opening state %v found", |
|
chanID, channelState) |
|
|
|
if channel.IsPending { |
|
// Set up the channel barriers again, to make sure |
|
// waitUntilChannelOpen correctly waits until the |
|
// opening process is completely over. |
|
f.barrierMtx.Lock() |
|
fndgLog.Tracef("Loading pending ChannelPoint(%v), "+ |
|
"creating chan barrier", channel.FundingOutpoint) |
|
f.newChanBarriers[chanID] = make(chan struct{}) |
|
f.barrierMtx.Unlock() |
|
} |
|
|
|
// If we did find the channel in the opening state database, we |
|
// have seen the funding transaction being confirmed, but we |
|
// did not finish the rest of the setup procedure before we shut |
|
// down. We handle the remaining steps of this setup by |
|
// continuing the procedure where we left off. |
|
switch channelState { |
|
case markedOpen: |
|
// The funding transaction was confirmed, but we did not |
|
// successfully send the fundingLocked message to the |
|
// peer, so let's do that now. |
|
f.wg.Add(1) |
|
go func(dbChan *channeldb.OpenChannel) { |
|
defer f.wg.Done() |
|
|
|
peerChan := make(chan lnpeer.Peer, 1) |
|
f.cfg.NotifyWhenOnline(dbChan.IdentityPub, peerChan) |
|
|
|
var peer lnpeer.Peer |
|
select { |
|
case peer = <-peerChan: |
|
case <-f.quit: |
|
return |
|
} |
|
err := f.handleFundingConfirmation( |
|
peer, dbChan, shortChanID, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("Failed to handle "+ |
|
"funding confirmation: %v", err) |
|
return |
|
} |
|
}(channel) |
|
|
|
case fundingLockedSent: |
|
// fundingLocked was sent to peer, but the channel |
|
// was not added to the router graph and the channel |
|
// announcement was not sent. |
|
f.wg.Add(1) |
|
go func(dbChan *channeldb.OpenChannel) { |
|
defer f.wg.Done() |
|
|
|
err = f.addToRouterGraph(dbChan, shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("failed adding to "+ |
|
"router graph: %v", err) |
|
return |
|
} |
|
|
|
// TODO(halseth): should create a state machine |
|
// that can more easily be resumed from |
|
// different states, to avoid this code |
|
// duplication. |
|
err = f.annAfterSixConfs(dbChan, shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("error sending channel "+ |
|
"announcements: %v", err) |
|
return |
|
} |
|
}(channel) |
|
|
|
case addedToRouterGraph: |
|
// The channel was added to the Router's topology, but |
|
// the channel announcement was not sent. |
|
f.wg.Add(1) |
|
go func(dbChan *channeldb.OpenChannel) { |
|
defer f.wg.Done() |
|
|
|
err = f.annAfterSixConfs(dbChan, shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("error sending channel "+ |
|
"announcement: %v", err) |
|
return |
|
} |
|
}(channel) |
|
|
|
default: |
|
fndgLog.Errorf("undefined channelState: %v", |
|
channelState) |
|
} |
|
} |
|
|
|
f.wg.Add(1) // TODO(roasbeef): tune |
|
go f.reservationCoordinator() |
|
|
|
return nil |
|
} |
|
|
|
// Stop signals all helper goroutines to execute a graceful shutdown. This |
|
// method will block until all goroutines have exited. |
|
func (f *fundingManager) Stop() error { |
|
if atomic.AddInt32(&f.stopped, 1) != 1 { |
|
return nil |
|
} |
|
|
|
fndgLog.Infof("Funding manager shutting down") |
|
|
|
close(f.quit) |
|
f.wg.Wait() |
|
|
|
return nil |
|
} |
|
|
|
// nextPendingChanID returns the next free pending channel ID to be used to |
|
// identify a particular future channel funding workflow. |
|
func (f *fundingManager) nextPendingChanID() [32]byte { |
|
// Obtain a fresh nonce. We do this by encoding the current nonce |
|
// counter, then incrementing it by one. |
|
f.nonceMtx.Lock() |
|
var nonce [8]byte |
|
binary.LittleEndian.PutUint64(nonce[:], f.chanIDNonce) |
|
f.chanIDNonce++ |
|
f.nonceMtx.Unlock() |
|
|
|
// We'll generate the next pending channelID by "encrypting" 32-bytes |
|
// of zeroes which'll extract 32 random bytes from our stream cipher. |
|
var ( |
|
nextChanID [32]byte |
|
zeroes [32]byte |
|
) |
|
salsa20.XORKeyStream(nextChanID[:], zeroes[:], nonce[:], &f.chanIDKey) |
|
|
|
return nextChanID |
|
} |
|
|
|
type pendingChannel struct { |
|
identityPub *btcec.PublicKey |
|
channelPoint *wire.OutPoint |
|
capacity btcutil.Amount |
|
localBalance btcutil.Amount |
|
remoteBalance btcutil.Amount |
|
} |
|
|
|
type pendingChansReq struct { |
|
resp chan []*pendingChannel |
|
err chan error |
|
} |
|
|
|
// PendingChannels returns a slice describing all the channels which are |
|
// currently pending at the last state of the funding workflow. |
|
func (f *fundingManager) PendingChannels() ([]*pendingChannel, error) { |
|
respChan := make(chan []*pendingChannel, 1) |
|
errChan := make(chan error, 1) |
|
|
|
req := &pendingChansReq{ |
|
resp: respChan, |
|
err: errChan, |
|
} |
|
|
|
select { |
|
case f.queries <- req: |
|
case <-f.quit: |
|
return nil, ErrFundingManagerShuttingDown |
|
} |
|
|
|
select { |
|
case resp := <-respChan: |
|
return resp, nil |
|
case err := <-errChan: |
|
return nil, err |
|
case <-f.quit: |
|
return nil, ErrFundingManagerShuttingDown |
|
} |
|
} |
|
|
|
// CancelPeerReservations cancels all active reservations associated with the |
|
// passed node. This will ensure any outputs which have been pre committed, |
|
// (and thus locked from coin selection), are properly freed. |
|
func (f *fundingManager) CancelPeerReservations(nodePub [33]byte) { |
|
|
|
fndgLog.Debugf("Cancelling all reservations for peer %x", nodePub[:]) |
|
|
|
f.resMtx.Lock() |
|
defer f.resMtx.Unlock() |
|
|
|
// We'll attempt to look up this node in the set of active |
|
// reservations. If they don't have any, then there's no further work |
|
// to be done. |
|
nodeReservations, ok := f.activeReservations[nodePub] |
|
if !ok { |
|
fndgLog.Debugf("No active reservations for node: %x", nodePub[:]) |
|
return |
|
} |
|
|
|
// If they do have any active reservations, then we'll cancel all of |
|
// them (which releases any locked UTXO's), and also delete it from the |
|
// reservation map. |
|
for pendingID, resCtx := range nodeReservations { |
|
if err := resCtx.reservation.Cancel(); err != nil { |
|
fndgLog.Errorf("unable to cancel reservation for "+ |
|
"node=%x: %v", nodePub[:], err) |
|
} |
|
|
|
resCtx.err <- fmt.Errorf("peer disconnected") |
|
delete(nodeReservations, pendingID) |
|
} |
|
|
|
// Finally, we'll delete the node itself from the set of reservations. |
|
delete(f.activeReservations, nodePub) |
|
} |
|
|
|
// failFundingFlow will fail the active funding flow with the target peer, |
|
// identified by its unique temporary channel ID. This method will send an |
|
// error to the remote peer, and also remove the reservation from our set of |
|
// pending reservations. |
|
// |
|
// TODO(roasbeef): if peer disconnects, and haven't yet broadcast funding |
|
// transaction, then all reservations should be cleared. |
|
func (f *fundingManager) failFundingFlow(peer lnpeer.Peer, tempChanID [32]byte, |
|
fundingErr error) { |
|
|
|
fndgLog.Debugf("Failing funding flow for pendingID=%x: %v", |
|
tempChanID, fundingErr) |
|
|
|
ctx, err := f.cancelReservationCtx(peer.IdentityKey(), tempChanID) |
|
if err != nil { |
|
fndgLog.Errorf("unable to cancel reservation: %v", err) |
|
} |
|
|
|
// In case the case where the reservation existed, send the funding |
|
// error on the error channel. |
|
if ctx != nil { |
|
ctx.err <- fundingErr |
|
} |
|
|
|
// We only send the exact error if it is part of out whitelisted set of |
|
// errors (lnwire.ErrorCode or lnwallet.ReservationError). |
|
var msg lnwire.ErrorData |
|
switch e := fundingErr.(type) { |
|
|
|
// Let the actual error message be sent to the remote. |
|
case lnwallet.ReservationError: |
|
msg = lnwire.ErrorData(e.Error()) |
|
|
|
// Send the status code. |
|
case lnwire.ErrorCode: |
|
msg = lnwire.ErrorData{byte(e)} |
|
|
|
// We just send a generic error. |
|
default: |
|
msg = lnwire.ErrorData("funding failed due to internal error") |
|
} |
|
|
|
errMsg := &lnwire.Error{ |
|
ChanID: tempChanID, |
|
Data: msg, |
|
} |
|
|
|
fndgLog.Debugf("Sending funding error to peer (%x): %v", |
|
peer.IdentityKey().SerializeCompressed(), spew.Sdump(errMsg)) |
|
if err := peer.SendMessage(false, errMsg); err != nil { |
|
fndgLog.Errorf("unable to send error message to peer %v", err) |
|
} |
|
} |
|
|
|
// reservationCoordinator is the primary goroutine tasked with progressing the |
|
// funding workflow between the wallet, and any outside peers or local callers. |
|
// |
|
// NOTE: This MUST be run as a goroutine. |
|
func (f *fundingManager) reservationCoordinator() { |
|
defer f.wg.Done() |
|
|
|
zombieSweepTicker := time.NewTicker(f.cfg.ZombieSweeperInterval) |
|
defer zombieSweepTicker.Stop() |
|
|
|
for { |
|
select { |
|
|
|
case msg := <-f.fundingMsgs: |
|
switch fmsg := msg.(type) { |
|
case *fundingOpenMsg: |
|
f.handleFundingOpen(fmsg) |
|
case *fundingAcceptMsg: |
|
f.handleFundingAccept(fmsg) |
|
case *fundingCreatedMsg: |
|
f.handleFundingCreated(fmsg) |
|
case *fundingSignedMsg: |
|
f.handleFundingSigned(fmsg) |
|
case *fundingLockedMsg: |
|
f.wg.Add(1) |
|
go f.handleFundingLocked(fmsg) |
|
case *fundingErrorMsg: |
|
f.handleErrorMsg(fmsg) |
|
} |
|
case req := <-f.fundingRequests: |
|
f.handleInitFundingMsg(req) |
|
|
|
case <-zombieSweepTicker.C: |
|
f.pruneZombieReservations() |
|
|
|
case req := <-f.queries: |
|
switch msg := req.(type) { |
|
case *pendingChansReq: |
|
f.handlePendingChannels(msg) |
|
} |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
} |
|
|
|
// handlePendingChannels responds to a request for details concerning all |
|
// currently pending channels waiting for the final phase of the funding |
|
// workflow (funding txn confirmation). |
|
func (f *fundingManager) handlePendingChannels(msg *pendingChansReq) { |
|
var pendingChannels []*pendingChannel |
|
|
|
dbPendingChannels, err := f.cfg.Wallet.Cfg.Database.FetchPendingChannels() |
|
if err != nil { |
|
msg.err <- err |
|
return |
|
} |
|
|
|
for _, dbPendingChan := range dbPendingChannels { |
|
pendingChan := &pendingChannel{ |
|
identityPub: dbPendingChan.IdentityPub, |
|
channelPoint: &dbPendingChan.FundingOutpoint, |
|
capacity: dbPendingChan.Capacity, |
|
localBalance: dbPendingChan.LocalCommitment.LocalBalance.ToSatoshis(), |
|
remoteBalance: dbPendingChan.LocalCommitment.RemoteBalance.ToSatoshis(), |
|
} |
|
|
|
pendingChannels = append(pendingChannels, pendingChan) |
|
} |
|
|
|
msg.resp <- pendingChannels |
|
} |
|
|
|
// processFundingOpen sends a message to the fundingManager allowing it to |
|
// initiate the new funding workflow with the source peer. |
|
func (f *fundingManager) processFundingOpen(msg *lnwire.OpenChannel, |
|
peer lnpeer.Peer) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingOpenMsg{msg, peer}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleFundingOpen creates an initial 'ChannelReservation' within the wallet, |
|
// then responds to the source peer with an accept channel message progressing |
|
// the funding workflow. |
|
// |
|
// TODO(roasbeef): add error chan to all, let channelManager handle |
|
// error+propagate |
|
func (f *fundingManager) handleFundingOpen(fmsg *fundingOpenMsg) { |
|
// Check number of pending channels to be smaller than maximum allowed |
|
// number and send ErrorGeneric to remote peer if condition is |
|
// violated. |
|
peerPubKey := fmsg.peer.IdentityKey() |
|
peerIDKey := newSerializedKey(peerPubKey) |
|
|
|
msg := fmsg.msg |
|
amt := msg.FundingAmount |
|
|
|
// We count the number of pending channels for this peer. This is the |
|
// sum of the active reservations and the channels pending open in the |
|
// database. |
|
f.resMtx.RLock() |
|
numPending := len(f.activeReservations[peerIDKey]) |
|
f.resMtx.RUnlock() |
|
|
|
channels, err := f.cfg.Wallet.Cfg.Database.FetchOpenChannels(peerPubKey) |
|
if err != nil { |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, err, |
|
) |
|
return |
|
} |
|
|
|
for _, c := range channels { |
|
if c.IsPending { |
|
numPending++ |
|
} |
|
} |
|
|
|
// TODO(roasbeef): modify to only accept a _single_ pending channel per |
|
// block unless white listed |
|
if numPending >= cfg.MaxPendingChannels { |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, |
|
lnwire.ErrMaxPendingChannels, |
|
) |
|
return |
|
} |
|
|
|
// We'll also reject any requests to create channels until we're fully |
|
// synced to the network as we won't be able to properly validate the |
|
// confirmation of the funding transaction. |
|
isSynced, _, err := f.cfg.Wallet.IsSynced() |
|
if err != nil || !isSynced { |
|
if err != nil { |
|
fndgLog.Errorf("unable to query wallet: %v", err) |
|
} |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, |
|
lnwire.ErrSynchronizingChain, |
|
) |
|
return |
|
} |
|
|
|
// We'll reject any request to create a channel that's above the |
|
// current soft-limit for channel size. |
|
if msg.FundingAmount > maxFundingAmount { |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, |
|
lnwire.ErrChanTooLarge, |
|
) |
|
return |
|
} |
|
|
|
// We'll, also ensure that the remote party isn't attempting to propose |
|
// a channel that's below our current min channel size. |
|
if amt < f.cfg.MinChanSize { |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, |
|
lnwallet.ErrChanTooSmall(amt, btcutil.Amount(f.cfg.MinChanSize)), |
|
) |
|
return |
|
} |
|
|
|
// If request specifies non-zero push amount and 'rejectpush' is set, |
|
// signal an error. |
|
if cfg.RejectPush && msg.PushAmount > 0 { |
|
f.failFundingFlow( |
|
fmsg.peer, fmsg.msg.PendingChannelID, |
|
lnwallet.ErrNonZeroPushAmount()) |
|
return |
|
} |
|
|
|
fndgLog.Infof("Recv'd fundingRequest(amt=%v, push=%v, delay=%v, "+ |
|
"pendingId=%x) from peer(%x)", amt, msg.PushAmount, |
|
msg.CsvDelay, msg.PendingChannelID, |
|
fmsg.peer.IdentityKey().SerializeCompressed()) |
|
|
|
// Attempt to initialize a reservation within the wallet. If the wallet |
|
// has insufficient resources to create the channel, then 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 |
|
// funds to the channel ourselves. |
|
chainHash := chainhash.Hash(msg.ChainHash) |
|
req := &lnwallet.InitFundingReserveMsg{ |
|
ChainHash: &chainHash, |
|
NodeID: fmsg.peer.IdentityKey(), |
|
NodeAddr: fmsg.peer.Address(), |
|
FundingAmount: 0, |
|
Capacity: amt, |
|
CommitFeePerKw: lnwallet.SatPerKWeight(msg.FeePerKiloWeight), |
|
FundingFeePerKw: 0, |
|
PushMSat: msg.PushAmount, |
|
Flags: msg.ChannelFlags, |
|
MinConfs: 1, |
|
} |
|
|
|
reservation, err := f.cfg.Wallet.InitChannelReservation(req) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to initialize reservation: %v", err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
// 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. |
|
channelConstraints := &channeldb.ChannelConstraints{ |
|
DustLimit: msg.DustLimit, |
|
ChanReserve: msg.ChannelReserve, |
|
MaxPendingAmount: msg.MaxValueInFlight, |
|
MinHTLC: msg.HtlcMinimum, |
|
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs, |
|
CsvDelay: msg.CsvDelay, |
|
} |
|
err = reservation.CommitConstraints(channelConstraints) |
|
if err != nil { |
|
fndgLog.Errorf("Unacceptable channel constraints: %v", err) |
|
f.failFundingFlow(fmsg.peer, fmsg.msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
fndgLog.Infof("Requiring %v confirmations for pendingChan(%x): "+ |
|
"amt=%v, push_amt=%v", numConfsReq, fmsg.msg.PendingChannelID, |
|
amt, msg.PushAmount) |
|
|
|
// Generate our required constraints for the remote party. |
|
remoteCsvDelay := f.cfg.RequiredRemoteDelay(amt) |
|
chanReserve := f.cfg.RequiredRemoteChanReserve(amt, msg.DustLimit) |
|
maxValue := f.cfg.RequiredRemoteMaxValue(amt) |
|
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(amt) |
|
minHtlc := f.cfg.DefaultRoutingPolicy.MinHTLC |
|
|
|
// Once the reservation has been created successfully, we add it to |
|
// this peer's map of pending reservations to track this particular |
|
// reservation until either abort or completion. |
|
f.resMtx.Lock() |
|
if _, ok := f.activeReservations[peerIDKey]; !ok { |
|
f.activeReservations[peerIDKey] = make(pendingChannels) |
|
} |
|
resCtx := &reservationWithCtx{ |
|
reservation: reservation, |
|
chanAmt: amt, |
|
remoteCsvDelay: remoteCsvDelay, |
|
remoteMinHtlc: minHtlc, |
|
err: make(chan error, 1), |
|
peer: fmsg.peer, |
|
} |
|
f.activeReservations[peerIDKey][msg.PendingChannelID] = resCtx |
|
f.resMtx.Unlock() |
|
|
|
// Update the timestamp once the fundingOpenMsg has been handled. |
|
defer resCtx.updateTimestamp() |
|
|
|
// With our parameters set, we'll now process their contribution so we |
|
// can move the funding workflow ahead. |
|
remoteContribution := &lnwallet.ChannelContribution{ |
|
FundingAmount: amt, |
|
FirstCommitmentPoint: msg.FirstCommitmentPoint, |
|
ChannelConfig: &channeldb.ChannelConfig{ |
|
ChannelConstraints: channeldb.ChannelConstraints{ |
|
DustLimit: msg.DustLimit, |
|
MaxPendingAmount: maxValue, |
|
ChanReserve: chanReserve, |
|
MinHTLC: minHtlc, |
|
MaxAcceptedHtlcs: maxHtlcs, |
|
CsvDelay: remoteCsvDelay, |
|
}, |
|
MultiSigKey: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.FundingKey), |
|
}, |
|
RevocationBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.RevocationPoint), |
|
}, |
|
PaymentBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.PaymentPoint), |
|
}, |
|
DelayBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.DelayedPaymentPoint), |
|
}, |
|
HtlcBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.HtlcPoint), |
|
}, |
|
}, |
|
} |
|
err = reservation.ProcessSingleContribution(remoteContribution) |
|
if err != nil { |
|
fndgLog.Errorf("unable to add contribution reservation: %v", err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
fndgLog.Infof("Sending fundingResp for pendingID(%x)", |
|
msg.PendingChannelID) |
|
fndgLog.Debugf("Remote party accepted commitment constraints: %v", |
|
spew.Sdump(remoteContribution.ChannelConfig.ChannelConstraints)) |
|
|
|
// With the initiator's contribution recorded, respond with our |
|
// contribution in the next message of the workflow. |
|
ourContribution := reservation.OurContribution() |
|
fundingAccept := lnwire.AcceptChannel{ |
|
PendingChannelID: msg.PendingChannelID, |
|
DustLimit: ourContribution.DustLimit, |
|
MaxValueInFlight: maxValue, |
|
ChannelReserve: chanReserve, |
|
MinAcceptDepth: uint32(numConfsReq), |
|
HtlcMinimum: minHtlc, |
|
CsvDelay: remoteCsvDelay, |
|
MaxAcceptedHTLCs: maxHtlcs, |
|
FundingKey: ourContribution.MultiSigKey.PubKey, |
|
RevocationPoint: ourContribution.RevocationBasePoint.PubKey, |
|
PaymentPoint: ourContribution.PaymentBasePoint.PubKey, |
|
DelayedPaymentPoint: ourContribution.DelayBasePoint.PubKey, |
|
HtlcPoint: ourContribution.HtlcBasePoint.PubKey, |
|
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, |
|
} |
|
if err := fmsg.peer.SendMessage(false, &fundingAccept); err != nil { |
|
fndgLog.Errorf("unable to send funding response to peer: %v", err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
} |
|
|
|
// processFundingAccept sends a message to the fundingManager allowing it to |
|
// continue the second phase of a funding workflow with the target peer. |
|
func (f *fundingManager) processFundingAccept(msg *lnwire.AcceptChannel, |
|
peer lnpeer.Peer) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingAcceptMsg{msg, peer}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleFundingAccept processes a response to the workflow initiation sent by |
|
// the remote peer. This message then queues a message with the funding |
|
// outpoint, and a commitment signature to the remote peer. |
|
func (f *fundingManager) handleFundingAccept(fmsg *fundingAcceptMsg) { |
|
msg := fmsg.msg |
|
pendingChanID := fmsg.msg.PendingChannelID |
|
peerKey := fmsg.peer.IdentityKey() |
|
|
|
resCtx, err := f.getReservationCtx(peerKey, pendingChanID) |
|
if err != nil { |
|
fndgLog.Warnf("Can't find reservation (peerKey:%v, chanID:%v)", |
|
peerKey, pendingChanID) |
|
return |
|
} |
|
|
|
// Update the timestamp once the fundingAcceptMsg has been handled. |
|
defer resCtx.updateTimestamp() |
|
|
|
fndgLog.Infof("Recv'd fundingResponse for pendingID(%x)", pendingChanID[:]) |
|
|
|
// The required number of confirmations should not be greater than the |
|
// maximum number of confirmations required by the ChainNotifier to |
|
// properly dispatch confirmations. |
|
if msg.MinAcceptDepth > chainntnfs.MaxNumConfs { |
|
err := lnwallet.ErrNumConfsTooLarge( |
|
msg.MinAcceptDepth, chainntnfs.MaxNumConfs, |
|
) |
|
fndgLog.Warnf("Unacceptable channel constraints: %v", err) |
|
f.failFundingFlow(fmsg.peer, fmsg.msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
// We'll also specify the responder's preference for the number of |
|
// required confirmations, and also the set of channel constraints |
|
// they've specified for commitment states we can create. |
|
resCtx.reservation.SetNumConfsRequired(uint16(msg.MinAcceptDepth)) |
|
channelConstraints := &channeldb.ChannelConstraints{ |
|
DustLimit: msg.DustLimit, |
|
ChanReserve: msg.ChannelReserve, |
|
MaxPendingAmount: msg.MaxValueInFlight, |
|
MinHTLC: msg.HtlcMinimum, |
|
MaxAcceptedHtlcs: msg.MaxAcceptedHTLCs, |
|
CsvDelay: msg.CsvDelay, |
|
} |
|
err = resCtx.reservation.CommitConstraints(channelConstraints) |
|
if err != nil { |
|
fndgLog.Warnf("Unacceptable channel constraints: %v", err) |
|
f.failFundingFlow(fmsg.peer, fmsg.msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
// As they've accepted our channel constraints, we'll regenerate them |
|
// here so we can properly commit their accepted constraints to the |
|
// reservation. |
|
chanReserve := f.cfg.RequiredRemoteChanReserve(resCtx.chanAmt, msg.DustLimit) |
|
maxValue := f.cfg.RequiredRemoteMaxValue(resCtx.chanAmt) |
|
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(resCtx.chanAmt) |
|
|
|
// The remote node has responded with their portion of the channel |
|
// contribution. At this point, we can process their contribution which |
|
// allows us to construct and sign both the commitment transaction, and |
|
// the funding transaction. |
|
remoteContribution := &lnwallet.ChannelContribution{ |
|
FirstCommitmentPoint: msg.FirstCommitmentPoint, |
|
ChannelConfig: &channeldb.ChannelConfig{ |
|
ChannelConstraints: channeldb.ChannelConstraints{ |
|
DustLimit: msg.DustLimit, |
|
MaxPendingAmount: maxValue, |
|
ChanReserve: chanReserve, |
|
MinHTLC: resCtx.remoteMinHtlc, |
|
MaxAcceptedHtlcs: maxHtlcs, |
|
CsvDelay: resCtx.remoteCsvDelay, |
|
}, |
|
MultiSigKey: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.FundingKey), |
|
}, |
|
RevocationBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.RevocationPoint), |
|
}, |
|
PaymentBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.PaymentPoint), |
|
}, |
|
DelayBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.DelayedPaymentPoint), |
|
}, |
|
HtlcBasePoint: keychain.KeyDescriptor{ |
|
PubKey: copyPubKey(msg.HtlcPoint), |
|
}, |
|
}, |
|
} |
|
err = resCtx.reservation.ProcessContribution(remoteContribution) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to process contribution from %v: %v", |
|
peerKey, err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
|
|
fndgLog.Infof("pendingChan(%x): remote party proposes num_confs=%v, "+ |
|
"csv_delay=%v", pendingChanID[:], msg.MinAcceptDepth, msg.CsvDelay) |
|
fndgLog.Debugf("Remote party accepted commitment constraints: %v", |
|
spew.Sdump(remoteContribution.ChannelConfig.ChannelConstraints)) |
|
|
|
// Now that we have their contribution, we can extract, then send over |
|
// both the funding out point and our signature for their version of |
|
// the commitment transaction to the remote peer. |
|
outPoint := resCtx.reservation.FundingOutpoint() |
|
_, sig := resCtx.reservation.OurSignatures() |
|
|
|
// A new channel has almost finished the funding process. In order to |
|
// properly synchronize with the writeHandler goroutine, we add a new |
|
// channel to the barriers map which will be closed once the channel is |
|
// fully open. |
|
f.barrierMtx.Lock() |
|
channelID := lnwire.NewChanIDFromOutPoint(outPoint) |
|
fndgLog.Debugf("Creating chan barrier for ChanID(%v)", channelID) |
|
f.newChanBarriers[channelID] = make(chan struct{}) |
|
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, |
|
pendingChanID[:]) |
|
|
|
fundingCreated := &lnwire.FundingCreated{ |
|
PendingChannelID: pendingChanID, |
|
FundingPoint: *outPoint, |
|
} |
|
fundingCreated.CommitSig, err = lnwire.NewSigFromRawSignature(sig) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to parse signature: %v", err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
if err := fmsg.peer.SendMessage(false, fundingCreated); err != nil { |
|
fndgLog.Errorf("Unable to send funding complete message: %v", err) |
|
f.failFundingFlow(fmsg.peer, msg.PendingChannelID, err) |
|
return |
|
} |
|
} |
|
|
|
// processFundingCreated queues a funding complete message coupled with the |
|
// source peer to the fundingManager. |
|
func (f *fundingManager) processFundingCreated(msg *lnwire.FundingCreated, |
|
peer lnpeer.Peer) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingCreatedMsg{msg, peer}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleFundingCreated progresses the funding workflow when the daemon is on |
|
// 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 |
|
// the funding transaction, progressing the workflow into the final stage. |
|
func (f *fundingManager) handleFundingCreated(fmsg *fundingCreatedMsg) { |
|
peerKey := fmsg.peer.IdentityKey() |
|
pendingChanID := fmsg.msg.PendingChannelID |
|
|
|
resCtx, err := f.getReservationCtx(peerKey, pendingChanID) |
|
if err != nil { |
|
fndgLog.Warnf("can't find reservation (peerID:%v, chanID:%x)", |
|
peerKey, pendingChanID[:]) |
|
return |
|
} |
|
|
|
// The channel initiator has responded with the funding outpoint of the |
|
// final funding transaction, as well as a signature for our version of |
|
// the commitment transaction. So at this point, we can validate the |
|
// initiator's commitment transaction, then send our own if it's valid. |
|
// TODO(roasbeef): make case (p vs P) consistent throughout |
|
fundingOut := fmsg.msg.FundingPoint |
|
fndgLog.Infof("completing pendingID(%x) with ChannelPoint(%v)", |
|
pendingChanID[:], fundingOut) |
|
|
|
// With all the necessary data available, attempt to advance the |
|
// funding workflow to the next stage. If this succeeds then the |
|
// funding transaction will broadcast after our next message. |
|
// CompleteReservationSingle will also mark the channel as 'IsPending' |
|
// in the database. |
|
commitSig := fmsg.msg.CommitSig.ToSignatureBytes() |
|
completeChan, err := resCtx.reservation.CompleteReservationSingle( |
|
&fundingOut, commitSig) |
|
if err != nil { |
|
// TODO(roasbeef): better error logging: peerID, channelID, etc. |
|
fndgLog.Errorf("unable to complete single reservation: %v", err) |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
return |
|
} |
|
|
|
// The channel is marked IsPending in the database, and can be removed |
|
// from the set of active reservations. |
|
f.deleteReservationCtx(peerKey, fmsg.msg.PendingChannelID) |
|
|
|
// If something goes wrong before the funding transaction is confirmed, |
|
// we use this convenience method to delete the pending OpenChannel |
|
// from the database. |
|
deleteFromDatabase := func() { |
|
localBalance := completeChan.LocalCommitment.LocalBalance.ToSatoshis() |
|
closeInfo := &channeldb.ChannelCloseSummary{ |
|
ChanPoint: completeChan.FundingOutpoint, |
|
ChainHash: completeChan.ChainHash, |
|
RemotePub: completeChan.IdentityPub, |
|
CloseType: channeldb.FundingCanceled, |
|
Capacity: completeChan.Capacity, |
|
SettledBalance: localBalance, |
|
RemoteCurrentRevocation: completeChan.RemoteCurrentRevocation, |
|
RemoteNextRevocation: completeChan.RemoteNextRevocation, |
|
LocalChanConfig: completeChan.LocalChanCfg, |
|
} |
|
|
|
if err := completeChan.CloseChannel(closeInfo); err != nil { |
|
fndgLog.Errorf("Failed closing channel %v: %v", |
|
completeChan.FundingOutpoint, err) |
|
} |
|
} |
|
|
|
// A new channel has almost finished the funding process. In order to |
|
// properly synchronize with the writeHandler goroutine, we add a new |
|
// channel to the barriers map which will be closed once the channel is |
|
// fully open. |
|
f.barrierMtx.Lock() |
|
channelID := lnwire.NewChanIDFromOutPoint(&fundingOut) |
|
fndgLog.Debugf("Creating chan barrier for ChanID(%v)", channelID) |
|
f.newChanBarriers[channelID] = make(chan struct{}) |
|
f.barrierMtx.Unlock() |
|
|
|
fndgLog.Infof("sending FundingSigned for pendingID(%x) over "+ |
|
"ChannelPoint(%v)", pendingChanID[:], fundingOut) |
|
|
|
// With their signature for our version of the commitment transaction |
|
// verified, we can now send over our signature to the remote peer. |
|
_, sig := resCtx.reservation.OurSignatures() |
|
ourCommitSig, err := lnwire.NewSigFromRawSignature(sig) |
|
if err != nil { |
|
fndgLog.Errorf("unable to parse signature: %v", err) |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
deleteFromDatabase() |
|
return |
|
} |
|
|
|
fundingSigned := &lnwire.FundingSigned{ |
|
ChanID: channelID, |
|
CommitSig: ourCommitSig, |
|
} |
|
if err := fmsg.peer.SendMessage(false, fundingSigned); err != nil { |
|
fndgLog.Errorf("unable to send FundingSigned message: %v", err) |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
deleteFromDatabase() |
|
return |
|
} |
|
|
|
// Now that we've sent over our final signature for this channel, we'll |
|
// send it to the ChainArbitrator so it can watch for any on-chain |
|
// actions during this final confirmation stage. |
|
if err := f.cfg.WatchNewChannel(completeChan, peerKey); err != nil { |
|
fndgLog.Errorf("Unable to send new ChannelPoint(%v) for "+ |
|
"arbitration: %v", fundingOut, err) |
|
} |
|
|
|
// Create an entry in the local discovery map so we can ensure that we |
|
// process the channel confirmation fully before we receive a funding |
|
// locked message. |
|
f.localDiscoveryMtx.Lock() |
|
f.localDiscoverySignals[channelID] = make(chan struct{}) |
|
f.localDiscoveryMtx.Unlock() |
|
|
|
// At this point we have sent our last funding message to the |
|
// initiating peer before the funding transaction will be broadcast. |
|
// 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. |
|
// |
|
// When we get to this point we have sent the signComplete message to |
|
// the channel funder, and BOLT#2 specifies that we MUST remember the |
|
// channel for reconnection. The channel is already marked |
|
// as pending in the database, so in case of a disconnect or restart, |
|
// we will continue waiting for the confirmation the next time we start |
|
// the funding manager. In case the funding transaction never appears |
|
// on the blockchain, we must forget this channel. We therefore |
|
// completely forget about this channel if we haven't seen the funding |
|
// transaction in 288 blocks (~ 48 hrs), by canceling the reservation |
|
// and canceling the wait for the funding confirmation. |
|
f.wg.Add(1) |
|
go func() { |
|
defer f.wg.Done() |
|
confChan := make(chan *lnwire.ShortChannelID) |
|
timeoutChan := make(chan struct{}) |
|
go f.waitForFundingWithTimeout(completeChan, confChan, |
|
timeoutChan) |
|
|
|
var shortChanID *lnwire.ShortChannelID |
|
var ok bool |
|
select { |
|
case <-timeoutChan: |
|
// We did not see the funding confirmation before |
|
// timeout, so we forget the channel. |
|
err := fmt.Errorf("timeout waiting for funding tx "+ |
|
"(%v) to confirm", completeChan.FundingOutpoint) |
|
fndgLog.Warnf(err.Error()) |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
deleteFromDatabase() |
|
return |
|
case <-f.quit: |
|
// The fundingManager is shutting down, will resume |
|
// wait for funding transaction on startup. |
|
return |
|
case shortChanID, ok = <-confChan: |
|
if !ok { |
|
fndgLog.Errorf("waiting for funding confirmation" + |
|
" failed") |
|
return |
|
} |
|
// Fallthrough. |
|
} |
|
|
|
// Success, funding transaction was confirmed. |
|
err := f.handleFundingConfirmation( |
|
fmsg.peer, completeChan, shortChanID, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("failed to handle funding"+ |
|
"confirmation: %v", err) |
|
return |
|
} |
|
}() |
|
} |
|
|
|
// processFundingSigned sends a single funding sign complete message along with |
|
// the source peer to the funding manager. |
|
func (f *fundingManager) processFundingSigned(msg *lnwire.FundingSigned, |
|
peer lnpeer.Peer) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingSignedMsg{msg, peer}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleFundingSigned processes the final message received in a single funder |
|
// workflow. Once this message is processed, the funding transaction is |
|
// broadcast. Once the funding transaction reaches a sufficient number of |
|
// confirmations, a message is sent to the responding peer along with a compact |
|
// encoding of the location of the channel within the blockchain. |
|
func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { |
|
// As the funding signed message will reference the reservation by its |
|
// 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 { |
|
err := fmt.Errorf("Unable to find signed reservation for "+ |
|
"chan_id=%x", fmsg.msg.ChanID) |
|
fndgLog.Warnf(err.Error()) |
|
f.failFundingFlow(fmsg.peer, fmsg.msg.ChanID, err) |
|
return |
|
} |
|
|
|
peerKey := fmsg.peer.IdentityKey() |
|
resCtx, err := f.getReservationCtx(peerKey, pendingChanID) |
|
if err != nil { |
|
fndgLog.Warnf("Unable to find reservation (peerID:%v, chanID:%x)", |
|
peerKey, pendingChanID[:]) |
|
// TODO: add ErrChanNotFound? |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
return |
|
} |
|
|
|
// Create an entry in the local discovery map so we can ensure that we |
|
// process the channel confirmation fully before we receive a funding |
|
// locked message. |
|
fundingPoint := resCtx.reservation.FundingOutpoint() |
|
permChanID := lnwire.NewChanIDFromOutPoint(fundingPoint) |
|
f.localDiscoveryMtx.Lock() |
|
f.localDiscoverySignals[permChanID] = make(chan struct{}) |
|
f.localDiscoveryMtx.Unlock() |
|
|
|
// The remote peer has responded with a signature for our commitment |
|
// transaction. We'll verify the signature for validity, then commit |
|
// the state to disk as we can now open the channel. |
|
commitSig := fmsg.msg.CommitSig.ToSignatureBytes() |
|
completeChan, err := resCtx.reservation.CompleteReservation( |
|
nil, commitSig, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to complete reservation sign "+ |
|
"complete: %v", err) |
|
f.failFundingFlow(fmsg.peer, pendingChanID, err) |
|
return |
|
} |
|
|
|
// The channel is now marked IsPending in the database, and we can |
|
// delete it from our set of active reservations. |
|
f.deleteReservationCtx(peerKey, pendingChanID) |
|
|
|
// Broadcast the finalized funding transaction to the network. |
|
fundingTx := completeChan.FundingTxn |
|
fndgLog.Infof("Broadcasting funding tx for ChannelPoint(%v): %v", |
|
completeChan.FundingOutpoint, spew.Sdump(fundingTx)) |
|
|
|
err = f.cfg.PublishTransaction(fundingTx) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to broadcast funding tx for "+ |
|
"ChannelPoint(%v): %v", completeChan.FundingOutpoint, |
|
err) |
|
// We failed to broadcast the funding transaction, but watch |
|
// the channel regardless, in case the transaction made it to |
|
// the network. We will retry broadcast at startup. |
|
// TODO(halseth): retry more often? Handle with CPFP? Just |
|
// delete from the DB? |
|
} |
|
|
|
// Now that we have a finalized reservation for this funding flow, |
|
// we'll send the to be active channel to the ChainArbitrator so it can |
|
// watch for any on-chin actions before the channel has fully |
|
// confirmed. |
|
if err := f.cfg.WatchNewChannel(completeChan, peerKey); err != nil { |
|
fndgLog.Errorf("Unable to send new ChannelPoint(%v) for "+ |
|
"arbitration: %v", fundingPoint, err) |
|
} |
|
|
|
fndgLog.Infof("Finalizing pendingID(%x) over ChannelPoint(%v), "+ |
|
"waiting for channel open on-chain", pendingChanID[:], |
|
fundingPoint) |
|
|
|
// Send an update to the upstream client that the negotiation process |
|
// is over. |
|
// TODO(roasbeef): add abstraction over updates to accommodate |
|
// long-polling, or SSE, etc. |
|
upd := &lnrpc.OpenStatusUpdate{ |
|
Update: &lnrpc.OpenStatusUpdate_ChanPending{ |
|
ChanPending: &lnrpc.PendingUpdate{ |
|
Txid: fundingPoint.Hash[:], |
|
OutputIndex: fundingPoint.Index, |
|
}, |
|
}, |
|
} |
|
|
|
select { |
|
case resCtx.updates <- upd: |
|
case <-f.quit: |
|
return |
|
} |
|
|
|
// At this point we have broadcast the funding transaction and done all |
|
// necessary processing. |
|
f.wg.Add(1) |
|
go func() { |
|
defer f.wg.Done() |
|
confChan := make(chan *lnwire.ShortChannelID) |
|
cancelChan := make(chan struct{}) |
|
|
|
// In case the fundingManager is stopped at some point during |
|
// the remaining part of the opening process, we must wait for |
|
// this process to finish (either successfully or with some |
|
// error), before the fundingManager can be shut down. |
|
f.wg.Add(1) |
|
go func() { |
|
defer f.wg.Done() |
|
f.waitForFundingConfirmation(completeChan, cancelChan, |
|
confChan) |
|
}() |
|
|
|
var shortChanID *lnwire.ShortChannelID |
|
var ok bool |
|
select { |
|
case <-f.quit: |
|
return |
|
case shortChanID, ok = <-confChan: |
|
if !ok { |
|
fndgLog.Errorf("waiting for funding confirmation" + |
|
" failed") |
|
return |
|
} |
|
} |
|
|
|
fndgLog.Debugf("Channel with ShortChanID %v now confirmed", |
|
shortChanID.ToUint64()) |
|
|
|
// Go on adding the channel to the channel graph, and crafting |
|
// channel announcements. |
|
lnChannel, err := lnwallet.NewLightningChannel( |
|
nil, nil, completeChan, nil, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("failed creating lnChannel: %v", err) |
|
return |
|
} |
|
|
|
err = f.sendFundingLocked( |
|
fmsg.peer, completeChan, lnChannel, shortChanID, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("failed sending fundingLocked: %v", err) |
|
return |
|
} |
|
fndgLog.Debugf("FundingLocked for channel with ShortChanID "+ |
|
"%v sent", shortChanID.ToUint64()) |
|
|
|
err = f.addToRouterGraph(completeChan, shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("failed adding to router graph: %v", err) |
|
return |
|
} |
|
fndgLog.Debugf("Channel with ShortChanID %v added to "+ |
|
"router graph", shortChanID.ToUint64()) |
|
|
|
// Give the caller a final update notifying them that |
|
// the channel is now open. |
|
// TODO(roasbeef): only notify after recv of funding locked? |
|
upd := &lnrpc.OpenStatusUpdate{ |
|
Update: &lnrpc.OpenStatusUpdate_ChanOpen{ |
|
ChanOpen: &lnrpc.ChannelOpenUpdate{ |
|
ChannelPoint: &lnrpc.ChannelPoint{ |
|
FundingTxid: &lnrpc.ChannelPoint_FundingTxidBytes{ |
|
FundingTxidBytes: fundingPoint.Hash[:], |
|
}, |
|
OutputIndex: fundingPoint.Index, |
|
}, |
|
}, |
|
}, |
|
} |
|
|
|
select { |
|
case resCtx.updates <- upd: |
|
case <-f.quit: |
|
return |
|
} |
|
|
|
err = f.annAfterSixConfs(completeChan, shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("failed sending channel announcement: %v", |
|
err) |
|
return |
|
} |
|
}() |
|
} |
|
|
|
// waitForFundingWithTimeout is a wrapper around waitForFundingConfirmation that |
|
// will cancel the wait for confirmation if we are not the channel initiator and |
|
// the maxWaitNumBlocksFundingConf has passed from bestHeight. |
|
// In the case of timeout, the timeoutChan will be closed. In case of error, |
|
// confChan will be closed. In case of success, a *lnwire.ShortChannelID will be |
|
// passed to confChan. |
|
func (f *fundingManager) waitForFundingWithTimeout(completeChan *channeldb.OpenChannel, |
|
confChan chan<- *lnwire.ShortChannelID, timeoutChan chan<- struct{}) { |
|
|
|
epochClient, err := f.cfg.Notifier.RegisterBlockEpochNtfn(nil) |
|
if err != nil { |
|
fndgLog.Errorf("unable to register for epoch notification: %v", |
|
err) |
|
close(confChan) |
|
return |
|
} |
|
|
|
defer epochClient.Cancel() |
|
|
|
waitingConfChan := make(chan *lnwire.ShortChannelID) |
|
cancelChan := make(chan struct{}) |
|
|
|
// Add this goroutine to wait group so we can be sure that it is |
|
// properly stopped before the funding manager can be shut down. |
|
f.wg.Add(1) |
|
go func() { |
|
defer f.wg.Done() |
|
f.waitForFundingConfirmation(completeChan, cancelChan, |
|
waitingConfChan) |
|
}() |
|
|
|
// On block maxHeight we will cancel the funding confirmation wait. |
|
maxHeight := completeChan.FundingBroadcastHeight + maxWaitNumBlocksFundingConf |
|
for { |
|
select { |
|
case epoch, ok := <-epochClient.Epochs: |
|
if !ok { |
|
fndgLog.Warnf("Epoch client shutting down") |
|
return |
|
} |
|
|
|
// If we are not the channel initiator it's safe |
|
// to timeout the channel |
|
if uint32(epoch.Height) >= maxHeight && !completeChan.IsInitiator { |
|
fndgLog.Warnf("waited for %v blocks without "+ |
|
"seeing funding transaction confirmed,"+ |
|
" cancelling.", maxWaitNumBlocksFundingConf) |
|
|
|
// Cancel the waitForFundingConfirmation |
|
// goroutine. |
|
close(cancelChan) |
|
|
|
// Notify the caller of the timeout. |
|
close(timeoutChan) |
|
return |
|
} |
|
|
|
// TODO: If we are the channel initiator implement |
|
// a method for recovering the funds from the funding |
|
// transaction |
|
|
|
case <-f.quit: |
|
// The fundingManager is shutting down, will resume |
|
// waiting for the funding transaction on startup. |
|
return |
|
case shortChanID, ok := <-waitingConfChan: |
|
if !ok { |
|
// Failed waiting for confirmation, close |
|
// confChan to indicate failure. |
|
close(confChan) |
|
return |
|
} |
|
|
|
select { |
|
case confChan <- shortChanID: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
} |
|
} |
|
|
|
// makeFundingScript re-creates the funding script for the funding transaction |
|
// of the target channel. |
|
func makeFundingScript(channel *channeldb.OpenChannel) ([]byte, error) { |
|
localKey := channel.LocalChanCfg.MultiSigKey.PubKey.SerializeCompressed() |
|
remoteKey := channel.RemoteChanCfg.MultiSigKey.PubKey.SerializeCompressed() |
|
|
|
multiSigScript, err := input.GenMultiSigScript(localKey, remoteKey) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return input.WitnessScriptHash(multiSigScript) |
|
} |
|
|
|
// waitForFundingConfirmation handles the final stages of the channel funding |
|
// process once the funding transaction has been broadcast. The primary |
|
// function of waitForFundingConfirmation is to wait for blockchain |
|
// confirmation, and then to notify the other systems that must be notified |
|
// when a channel has become active for lightning transactions. |
|
// The wait can be canceled by closing the cancelChan. In case of success, |
|
// a *lnwire.ShortChannelID will be passed to confChan. |
|
func (f *fundingManager) waitForFundingConfirmation(completeChan *channeldb.OpenChannel, |
|
cancelChan <-chan struct{}, confChan chan<- *lnwire.ShortChannelID) { |
|
|
|
defer close(confChan) |
|
|
|
// Register with the ChainNotifier for a notification once the funding |
|
// transaction reaches `numConfs` confirmations. |
|
txid := completeChan.FundingOutpoint.Hash |
|
fundingScript, err := makeFundingScript(completeChan) |
|
if err != nil { |
|
fndgLog.Errorf("unable to create funding script for "+ |
|
"ChannelPoint(%v): %v", completeChan.FundingOutpoint, err) |
|
return |
|
} |
|
numConfs := uint32(completeChan.NumConfsRequired) |
|
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( |
|
&txid, fundingScript, numConfs, completeChan.FundingBroadcastHeight, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to register for confirmation of "+ |
|
"ChannelPoint(%v): %v", completeChan.FundingOutpoint, err) |
|
return |
|
} |
|
|
|
fndgLog.Infof("Waiting for funding tx (%v) to reach %v confirmations", |
|
txid, numConfs) |
|
|
|
var confDetails *chainntnfs.TxConfirmation |
|
var ok bool |
|
|
|
// Wait until the specified number of confirmations has been reached, |
|
// we get a cancel signal, or the wallet signals a shutdown. |
|
select { |
|
case confDetails, ok = <-confNtfn.Confirmed: |
|
// fallthrough |
|
case <-cancelChan: |
|
fndgLog.Warnf("canceled waiting for funding confirmation, "+ |
|
"stopping funding flow for ChannelPoint(%v)", |
|
completeChan.FundingOutpoint) |
|
return |
|
case <-f.quit: |
|
fndgLog.Warnf("fundingManager shutting down, stopping funding "+ |
|
"flow for ChannelPoint(%v)", completeChan.FundingOutpoint) |
|
return |
|
} |
|
|
|
if !ok { |
|
fndgLog.Warnf("ChainNotifier shutting down, cannot complete "+ |
|
"funding flow for ChannelPoint(%v)", |
|
completeChan.FundingOutpoint) |
|
return |
|
} |
|
|
|
fundingPoint := completeChan.FundingOutpoint |
|
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) |
|
|
|
fndgLog.Infof("ChannelPoint(%v) is now active: ChannelID(%x)", |
|
fundingPoint, chanID[:]) |
|
|
|
// With the block height and the transaction index known, we can |
|
// construct the compact chanID which is used on the network to unique |
|
// identify channels. |
|
shortChanID := lnwire.ShortChannelID{ |
|
BlockHeight: confDetails.BlockHeight, |
|
TxIndex: confDetails.TxIndex, |
|
TxPosition: uint16(fundingPoint.Index), |
|
} |
|
|
|
// Now that the channel has been fully confirmed, we'll mark it as open |
|
// within the database. |
|
if err := completeChan.MarkAsOpen(shortChanID); err != nil { |
|
fndgLog.Errorf("error setting channel pending flag to false: "+ |
|
"%v", err) |
|
return |
|
} |
|
|
|
// Inform the ChannelNotifier that the channel has transitioned from |
|
// pending open to open. |
|
f.cfg.NotifyOpenChannelEvent(completeChan.FundingOutpoint) |
|
|
|
// TODO(roasbeef): ideally persistent state update for chan above |
|
// should be abstracted |
|
|
|
// The funding transaction now being confirmed, we add this channel to |
|
// the fundingManager's internal persistent state machine that we use |
|
// to track the remaining process of the channel opening. This is |
|
// useful to resume the opening process in case of restarts. |
|
// |
|
// TODO(halseth): make the two db transactions (MarkChannelAsOpen and |
|
// saveChannelOpeningState) atomic by doing them in the same transaction. |
|
// Needed to be properly fault-tolerant. |
|
err = f.saveChannelOpeningState(&completeChan.FundingOutpoint, markedOpen, |
|
&shortChanID) |
|
if err != nil { |
|
fndgLog.Errorf("error setting channel state to markedOpen: %v", |
|
err) |
|
return |
|
} |
|
|
|
// As there might already be an active link in the switch with an |
|
// outdated short chan ID, we'll instruct the switch to load the updated |
|
// short chan id from disk. |
|
err = f.cfg.ReportShortChanID(fundingPoint) |
|
if err != nil { |
|
fndgLog.Errorf("unable to report short chan id: %v", err) |
|
} |
|
|
|
select { |
|
case confChan <- &shortChanID: |
|
case <-f.quit: |
|
return |
|
} |
|
|
|
// Close the discoverySignal channel, indicating to a separate |
|
// goroutine that the channel now is marked as open in the database |
|
// and that it is acceptable to process funding locked messages |
|
// from the peer. |
|
f.localDiscoveryMtx.Lock() |
|
if discoverySignal, ok := f.localDiscoverySignals[chanID]; ok { |
|
close(discoverySignal) |
|
} |
|
f.localDiscoveryMtx.Unlock() |
|
} |
|
|
|
// handleFundingConfirmation is a wrapper method for creating a new |
|
// lnwallet.LightningChannel object, calling sendFundingLocked, |
|
// addToRouterGraph, and annAfterSixConfs. This is called after the funding |
|
// transaction is confirmed. |
|
func (f *fundingManager) handleFundingConfirmation(peer lnpeer.Peer, |
|
completeChan *channeldb.OpenChannel, |
|
shortChanID *lnwire.ShortChannelID) error { |
|
|
|
// We create the state-machine object which wraps the database state. |
|
lnChannel, err := lnwallet.NewLightningChannel( |
|
nil, nil, completeChan, nil, |
|
) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(&completeChan.FundingOutpoint) |
|
|
|
fndgLog.Debugf("ChannelID(%v) is now fully confirmed!", chanID) |
|
|
|
err = f.sendFundingLocked(peer, completeChan, lnChannel, shortChanID) |
|
if err != nil { |
|
return fmt.Errorf("failed sending fundingLocked: %v", err) |
|
} |
|
err = f.addToRouterGraph(completeChan, shortChanID) |
|
if err != nil { |
|
return fmt.Errorf("failed adding to router graph: %v", err) |
|
} |
|
err = f.annAfterSixConfs(completeChan, shortChanID) |
|
if err != nil { |
|
return fmt.Errorf("failed sending channel announcement: %v", |
|
err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// sendFundingLocked creates and sends the fundingLocked message. |
|
// This should be called after the funding transaction has been confirmed, |
|
// and the channelState is 'markedOpen'. |
|
func (f *fundingManager) sendFundingLocked(peer lnpeer.Peer, |
|
completeChan *channeldb.OpenChannel, channel *lnwallet.LightningChannel, |
|
shortChanID *lnwire.ShortChannelID) error { |
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(&completeChan.FundingOutpoint) |
|
peerKey := completeChan.IdentityPub |
|
|
|
// Next, we'll send over the funding locked message which marks that we |
|
// consider the channel open by presenting the remote party with our |
|
// next revocation key. Without the revocation key, the remote party |
|
// will be unable to propose state transitions. |
|
nextRevocation, err := channel.NextRevocationKey() |
|
if err != nil { |
|
return fmt.Errorf("unable to create next revocation: %v", err) |
|
} |
|
fundingLockedMsg := lnwire.NewFundingLocked(chanID, nextRevocation) |
|
|
|
// If the peer has disconnected before we reach this point, we will need |
|
// to wait for him to come back online before sending the fundingLocked |
|
// message. This is special for fundingLocked, since failing to send any |
|
// of the previous messages in the funding flow just cancels the flow. |
|
// But now the funding transaction is confirmed, the channel is open |
|
// and we have to make sure the peer gets the fundingLocked message when |
|
// it comes back online. This is also crucial during restart of lnd, |
|
// where we might try to resend the fundingLocked message before the |
|
// server has had the time to connect to the peer. We keep trying to |
|
// send fundingLocked until we succeed, or the fundingManager is shut |
|
// down. |
|
for { |
|
fndgLog.Debugf("Sending FundingLocked for ChannelID(%v) to "+ |
|
"peer %x", chanID, peerKey.SerializeCompressed()) |
|
|
|
if err := peer.SendMessage(false, fundingLockedMsg); err == nil { |
|
// Sending succeeded, we can break out and continue the |
|
// funding flow. |
|
break |
|
} |
|
|
|
fndgLog.Warnf("Unable to send fundingLocked to peer %x: %v. "+ |
|
"Will retry when online", peerKey.SerializeCompressed(), |
|
err) |
|
|
|
connected := make(chan lnpeer.Peer, 1) |
|
f.cfg.NotifyWhenOnline(completeChan.IdentityPub, connected) |
|
|
|
select { |
|
case <-connected: |
|
fndgLog.Infof("Peer(%x) came back online, will retry "+ |
|
"sending FundingLocked for ChannelID(%v)", |
|
peerKey.SerializeCompressed(), chanID) |
|
|
|
// Retry sending. |
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
} |
|
|
|
// As the fundingLocked message is now sent to the peer, the channel is |
|
// moved to the next state of the state machine. It will be moved to the |
|
// last state (actually deleted from the database) after the channel is |
|
// finally announced. |
|
err = f.saveChannelOpeningState(&completeChan.FundingOutpoint, |
|
fundingLockedSent, shortChanID) |
|
if err != nil { |
|
return fmt.Errorf("error setting channel state to"+ |
|
" fundingLockedSent: %v", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// addToRouterGraph sends a ChannelAnnouncement and a ChannelUpdate to the |
|
// gossiper so that the channel is added to the Router's internal graph. |
|
// These announcement messages are NOT broadcasted to the greater network, |
|
// only to the channel counter party. The proofs required to announce the |
|
// channel to the greater network will be created and sent in annAfterSixConfs. |
|
func (f *fundingManager) addToRouterGraph(completeChan *channeldb.OpenChannel, |
|
shortChanID *lnwire.ShortChannelID) error { |
|
|
|
chanID := lnwire.NewChanIDFromOutPoint(&completeChan.FundingOutpoint) |
|
|
|
// We'll obtain the min HTLC value we can forward in our direction, as |
|
// we'll use this value within our ChannelUpdate. This constraint is |
|
// originally set by the remote node, as it will be the one that will |
|
// need to determine the smallest HTLC it deems economically relevant. |
|
fwdMinHTLC := completeChan.LocalChanCfg.MinHTLC |
|
|
|
// We'll obtain the max HTLC value we can forward in our direction, as |
|
// we'll use this value within our ChannelUpdate. This value must be <= |
|
// channel capacity and <= the maximum in-flight msats set by the peer. |
|
fwdMaxHTLC := completeChan.LocalChanCfg.MaxPendingAmount |
|
capacityMSat := lnwire.NewMSatFromSatoshis(completeChan.Capacity) |
|
if fwdMaxHTLC > capacityMSat { |
|
fwdMaxHTLC = capacityMSat |
|
} |
|
|
|
ann, err := f.newChanAnnouncement( |
|
f.cfg.IDKey, completeChan.IdentityPub, |
|
completeChan.LocalChanCfg.MultiSigKey.PubKey, |
|
completeChan.RemoteChanCfg.MultiSigKey.PubKey, *shortChanID, |
|
chanID, fwdMinHTLC, fwdMaxHTLC, |
|
) |
|
if err != nil { |
|
return fmt.Errorf("error generating channel "+ |
|
"announcement: %v", err) |
|
} |
|
|
|
// Send ChannelAnnouncement and ChannelUpdate to the gossiper to add |
|
// to the Router's topology. |
|
errChan := f.cfg.SendAnnouncement( |
|
ann.chanAnn, discovery.ChannelCapacity(completeChan.Capacity), |
|
discovery.ChannelPoint(completeChan.FundingOutpoint), |
|
) |
|
select { |
|
case err := <-errChan: |
|
if err != nil { |
|
if routing.IsError(err, routing.ErrOutdated, |
|
routing.ErrIgnored) { |
|
fndgLog.Debugf("Router rejected "+ |
|
"ChannelAnnouncement: %v", err) |
|
} else { |
|
return fmt.Errorf("error sending channel "+ |
|
"announcement: %v", err) |
|
} |
|
} |
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
errChan = f.cfg.SendAnnouncement(ann.chanUpdateAnn) |
|
select { |
|
case err := <-errChan: |
|
if err != nil { |
|
if routing.IsError(err, routing.ErrOutdated, |
|
routing.ErrIgnored) { |
|
fndgLog.Debugf("Router rejected "+ |
|
"ChannelUpdate: %v", err) |
|
} else { |
|
return fmt.Errorf("error sending channel "+ |
|
"update: %v", err) |
|
} |
|
} |
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
// As the channel is now added to the ChannelRouter's topology, the |
|
// channel is moved to the next state of the state machine. It will be |
|
// moved to the last state (actually deleted from the database) after |
|
// the channel is finally announced. |
|
err = f.saveChannelOpeningState(&completeChan.FundingOutpoint, |
|
addedToRouterGraph, shortChanID) |
|
if err != nil { |
|
return fmt.Errorf("error setting channel state to"+ |
|
" addedToRouterGraph: %v", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// annAfterSixConfs broadcasts the necessary channel announcement messages to |
|
// the network after 6 confs. Should be called after the fundingLocked message |
|
// is sent and the channel is added to the router graph (channelState is |
|
// 'addedToRouterGraph') and the channel is ready to be used. This is the last |
|
// step in the channel opening process, and the opening state will be deleted |
|
// from the database if successful. |
|
func (f *fundingManager) annAfterSixConfs(completeChan *channeldb.OpenChannel, |
|
shortChanID *lnwire.ShortChannelID) error { |
|
|
|
// If this channel is not meant to be announced to the greater network, |
|
// we'll only send our NodeAnnouncement to our counterparty to ensure we |
|
// don't leak any of our information. |
|
announceChan := completeChan.ChannelFlags&lnwire.FFAnnounceChannel != 0 |
|
if !announceChan { |
|
fndgLog.Debugf("Will not announce private channel %v.", |
|
shortChanID.ToUint64()) |
|
|
|
peerChan := make(chan lnpeer.Peer, 1) |
|
f.cfg.NotifyWhenOnline(completeChan.IdentityPub, peerChan) |
|
|
|
var peer lnpeer.Peer |
|
select { |
|
case peer = <-peerChan: |
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
nodeAnn, err := f.cfg.CurrentNodeAnnouncement() |
|
if err != nil { |
|
return fmt.Errorf("unable to retrieve current node "+ |
|
"announcement: %v", err) |
|
} |
|
|
|
chanID := lnwire.NewChanIDFromOutPoint( |
|
&completeChan.FundingOutpoint, |
|
) |
|
pubKey := peer.PubKey() |
|
fndgLog.Debugf("Sending our NodeAnnouncement for "+ |
|
"ChannelID(%v) to %x", chanID, pubKey) |
|
|
|
if err := peer.SendMessage(true, &nodeAnn); err != nil { |
|
return fmt.Errorf("unable to send node announcement "+ |
|
"to peer %x: %v", pubKey, err) |
|
} |
|
} else { |
|
// Otherwise, we'll wait until the funding transaction has |
|
// reached 6 confirmations before announcing it. |
|
numConfs := uint32(completeChan.NumConfsRequired) |
|
if numConfs < 6 { |
|
numConfs = 6 |
|
} |
|
txid := completeChan.FundingOutpoint.Hash |
|
fndgLog.Debugf("Will announce channel %v after ChannelPoint"+ |
|
"(%v) has gotten %d confirmations", |
|
shortChanID.ToUint64(), completeChan.FundingOutpoint, |
|
numConfs) |
|
|
|
fundingScript, err := makeFundingScript(completeChan) |
|
if err != nil { |
|
return fmt.Errorf("unable to create funding script for "+ |
|
"ChannelPoint(%v): %v", |
|
completeChan.FundingOutpoint, err) |
|
} |
|
|
|
// Register with the ChainNotifier for a notification once the |
|
// funding transaction reaches at least 6 confirmations. |
|
confNtfn, err := f.cfg.Notifier.RegisterConfirmationsNtfn( |
|
&txid, fundingScript, numConfs, |
|
completeChan.FundingBroadcastHeight, |
|
) |
|
if err != nil { |
|
return fmt.Errorf("Unable to register for "+ |
|
"confirmation of ChannelPoint(%v): %v", |
|
completeChan.FundingOutpoint, err) |
|
} |
|
|
|
// Wait until 6 confirmations has been reached or the wallet |
|
// signals a shutdown. |
|
select { |
|
case _, ok := <-confNtfn.Confirmed: |
|
if !ok { |
|
return fmt.Errorf("ChainNotifier shutting "+ |
|
"down, cannot complete funding flow "+ |
|
"for ChannelPoint(%v)", |
|
completeChan.FundingOutpoint) |
|
} |
|
// Fallthrough. |
|
|
|
case <-f.quit: |
|
return fmt.Errorf("%v, stopping funding flow for "+ |
|
"ChannelPoint(%v)", |
|
ErrFundingManagerShuttingDown, |
|
completeChan.FundingOutpoint) |
|
} |
|
|
|
fundingPoint := completeChan.FundingOutpoint |
|
chanID := lnwire.NewChanIDFromOutPoint(&fundingPoint) |
|
|
|
fndgLog.Infof("Announcing ChannelPoint(%v), short_chan_id=%v", |
|
&fundingPoint, shortChanID) |
|
|
|
// We'll obtain the min HTLC value we can forward in our |
|
// direction, as we'll use this value within our ChannelUpdate. |
|
// This constraint is originally set by the remote node, as it |
|
// will be the one that will need to determine the smallest |
|
// HTLC it deems economically relevant. |
|
fwdMinHTLC := completeChan.LocalChanCfg.MinHTLC |
|
|
|
// We'll obtain the max HTLC value we can forward in our |
|
// direction, as we'll use this value within our ChannelUpdate. |
|
// This value must be <= channel capacity and <= the maximum |
|
// in-flight msats set by the peer. |
|
fwdMaxHTLC := completeChan.LocalChanCfg.MaxPendingAmount |
|
capacityMSat := lnwire.NewMSatFromSatoshis(completeChan.Capacity) |
|
if fwdMaxHTLC > capacityMSat { |
|
fwdMaxHTLC = capacityMSat |
|
} |
|
|
|
// Create and broadcast the proofs required to make this channel |
|
// public and usable for other nodes for routing. |
|
err = f.announceChannel( |
|
f.cfg.IDKey, completeChan.IdentityPub, |
|
completeChan.LocalChanCfg.MultiSigKey.PubKey, |
|
completeChan.RemoteChanCfg.MultiSigKey.PubKey, |
|
*shortChanID, chanID, fwdMinHTLC, fwdMaxHTLC, |
|
) |
|
if err != nil { |
|
return fmt.Errorf("channel announcement failed: %v", err) |
|
} |
|
|
|
fndgLog.Debugf("Channel with ChannelPoint(%v), short_chan_id=%v "+ |
|
"announced", &fundingPoint, shortChanID) |
|
} |
|
|
|
// We delete the channel opening state from our internal database |
|
// as the opening process has succeeded. We can do this because we |
|
// assume the AuthenticatedGossiper queues the announcement messages, |
|
// and persists them in case of a daemon shutdown. |
|
err := f.deleteChannelOpeningState(&completeChan.FundingOutpoint) |
|
if err != nil { |
|
return fmt.Errorf("error deleting channel state: %v", err) |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// processFundingLocked sends a message to the fundingManager allowing it to |
|
// finish the funding workflow. |
|
func (f *fundingManager) processFundingLocked(msg *lnwire.FundingLocked, |
|
peer lnpeer.Peer) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingLockedMsg{msg, peer}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleFundingLocked finalizes the channel funding process and enables the |
|
// channel to enter normal operating mode. |
|
func (f *fundingManager) handleFundingLocked(fmsg *fundingLockedMsg) { |
|
defer f.wg.Done() |
|
fndgLog.Debugf("Received FundingLocked for ChannelID(%v) from "+ |
|
"peer %x", fmsg.msg.ChanID, |
|
fmsg.peer.IdentityKey().SerializeCompressed()) |
|
|
|
// If we are currently in the process of handling a funding locked |
|
// message for this channel, ignore. |
|
f.handleFundingLockedMtx.Lock() |
|
_, ok := f.handleFundingLockedBarriers[fmsg.msg.ChanID] |
|
if ok { |
|
fndgLog.Infof("Already handling fundingLocked for "+ |
|
"ChannelID(%v), ignoring.", fmsg.msg.ChanID) |
|
f.handleFundingLockedMtx.Unlock() |
|
return |
|
} |
|
|
|
// If not already handling fundingLocked for this channel, set up |
|
// barrier, and move on. |
|
f.handleFundingLockedBarriers[fmsg.msg.ChanID] = struct{}{} |
|
f.handleFundingLockedMtx.Unlock() |
|
|
|
defer func() { |
|
f.handleFundingLockedMtx.Lock() |
|
delete(f.handleFundingLockedBarriers, fmsg.msg.ChanID) |
|
f.handleFundingLockedMtx.Unlock() |
|
}() |
|
|
|
f.localDiscoveryMtx.Lock() |
|
localDiscoverySignal, ok := f.localDiscoverySignals[fmsg.msg.ChanID] |
|
f.localDiscoveryMtx.Unlock() |
|
|
|
if ok { |
|
// Before we proceed with processing the funding locked |
|
// message, we'll wait for the local waitForFundingConfirmation |
|
// goroutine to signal that it has the necessary state in |
|
// place. Otherwise, we may be missing critical information |
|
// required to handle forwarded HTLC's. |
|
select { |
|
case <-localDiscoverySignal: |
|
// Fallthrough |
|
case <-f.quit: |
|
return |
|
} |
|
|
|
// With the signal received, we can now safely delete the entry |
|
// from the map. |
|
f.localDiscoveryMtx.Lock() |
|
delete(f.localDiscoverySignals, fmsg.msg.ChanID) |
|
f.localDiscoveryMtx.Unlock() |
|
} |
|
|
|
// First, we'll attempt to locate the channel who's funding workflow is |
|
// being finalized by this message. We got to the database rather than |
|
// our reservation map as we may have restarted, mid funding flow. |
|
chanID := fmsg.msg.ChanID |
|
channel, err := f.cfg.FindChannel(chanID) |
|
if err != nil { |
|
fndgLog.Errorf("Unable to locate ChannelID(%v), cannot complete "+ |
|
"funding", chanID) |
|
return |
|
} |
|
|
|
// If the RemoteNextRevocation is non-nil, it means that we have |
|
// already processed fundingLocked for this channel, so ignore. |
|
if channel.RemoteNextRevocation != nil { |
|
fndgLog.Infof("Received duplicate fundingLocked for "+ |
|
"ChannelID(%v), ignoring.", chanID) |
|
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.InsertNextRevocation(fmsg.msg.NextPerCommitmentPoint) |
|
if err != nil { |
|
fndgLog.Errorf("unable to insert next commitment point: %v", err) |
|
return |
|
} |
|
|
|
// Launch a defer so we _ensure_ that the channel barrier is properly |
|
// closed even if the target peer is not longer online at this point. |
|
defer func() { |
|
// Close the active channel barrier signalling the readHandler |
|
// that commitment related modifications to this channel can |
|
// now proceed. |
|
f.barrierMtx.Lock() |
|
chanBarrier, ok := f.newChanBarriers[chanID] |
|
if ok { |
|
fndgLog.Tracef("Closing chan barrier for ChanID(%v)", |
|
chanID) |
|
close(chanBarrier) |
|
delete(f.newChanBarriers, chanID) |
|
} |
|
f.barrierMtx.Unlock() |
|
}() |
|
|
|
if err := fmsg.peer.AddNewChannel(channel, f.quit); err != nil { |
|
fndgLog.Errorf("Unable to add new channel %v with peer %x: %v", |
|
fmsg.peer.IdentityKey().SerializeCompressed(), |
|
channel.FundingOutpoint, err) |
|
} |
|
} |
|
|
|
// channelProof is one half of the proof necessary to create an authenticated |
|
// announcement on the network. The two signatures individually sign a |
|
// statement of the existence of a channel. |
|
type channelProof struct { |
|
nodeSig *btcec.Signature |
|
bitcoinSig *btcec.Signature |
|
} |
|
|
|
// chanAnnouncement encapsulates the two authenticated announcements that we |
|
// send out to the network after a new channel has been created locally. |
|
type chanAnnouncement struct { |
|
chanAnn *lnwire.ChannelAnnouncement |
|
chanUpdateAnn *lnwire.ChannelUpdate |
|
chanProof *lnwire.AnnounceSignatures |
|
} |
|
|
|
// newChanAnnouncement creates the authenticated channel announcement messages |
|
// required to broadcast a newly created channel to the network. The |
|
// announcement is two part: the first part authenticates the existence of the |
|
// channel and contains four signatures binding the funding pub keys and |
|
// identity pub keys of both parties to the channel, and the second segment is |
|
// authenticated only by us and contains our directional routing policy for the |
|
// channel. |
|
func (f *fundingManager) newChanAnnouncement(localPubKey, remotePubKey, |
|
localFundingKey, remoteFundingKey *btcec.PublicKey, |
|
shortChanID lnwire.ShortChannelID, chanID lnwire.ChannelID, |
|
fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi) (*chanAnnouncement, error) { |
|
|
|
chainHash := *f.cfg.Wallet.Cfg.NetParams.GenesisHash |
|
|
|
// The unconditional section of the announcement is the ShortChannelID |
|
// itself which compactly encodes the location of the funding output |
|
// within the blockchain. |
|
chanAnn := &lnwire.ChannelAnnouncement{ |
|
ShortChannelID: shortChanID, |
|
Features: lnwire.NewRawFeatureVector(), |
|
ChainHash: chainHash, |
|
} |
|
|
|
// The chanFlags field indicates which directed edge of the channel is |
|
// being updated within the ChannelUpdateAnnouncement announcement |
|
// below. A value of zero means it's the edge of the "first" node and 1 |
|
// being the other node. |
|
var chanFlags lnwire.ChanUpdateChanFlags |
|
|
|
// The lexicographical ordering of the two identity public keys of the |
|
// nodes indicates which of the nodes is "first". If our serialized |
|
// identity key is lower than theirs then we're the "first" node and |
|
// second otherwise. |
|
selfBytes := localPubKey.SerializeCompressed() |
|
remoteBytes := remotePubKey.SerializeCompressed() |
|
if bytes.Compare(selfBytes, remoteBytes) == -1 { |
|
copy(chanAnn.NodeID1[:], localPubKey.SerializeCompressed()) |
|
copy(chanAnn.NodeID2[:], remotePubKey.SerializeCompressed()) |
|
copy(chanAnn.BitcoinKey1[:], localFundingKey.SerializeCompressed()) |
|
copy(chanAnn.BitcoinKey2[:], remoteFundingKey.SerializeCompressed()) |
|
|
|
// If we're the first node then update the chanFlags to |
|
// indicate the "direction" of the update. |
|
chanFlags = 0 |
|
} else { |
|
copy(chanAnn.NodeID1[:], remotePubKey.SerializeCompressed()) |
|
copy(chanAnn.NodeID2[:], localPubKey.SerializeCompressed()) |
|
copy(chanAnn.BitcoinKey1[:], remoteFundingKey.SerializeCompressed()) |
|
copy(chanAnn.BitcoinKey2[:], localFundingKey.SerializeCompressed()) |
|
|
|
// If we're the second node then update the chanFlags to |
|
// indicate the "direction" of the update. |
|
chanFlags = 1 |
|
} |
|
|
|
// Our channel update message flags will signal that we support the |
|
// max_htlc field. |
|
msgFlags := lnwire.ChanUpdateOptionMaxHtlc |
|
|
|
// We announce the channel with the default values. Some of |
|
// these values can later be changed by crafting a new ChannelUpdate. |
|
chanUpdateAnn := &lnwire.ChannelUpdate{ |
|
ShortChannelID: shortChanID, |
|
ChainHash: chainHash, |
|
Timestamp: uint32(time.Now().Unix()), |
|
MessageFlags: msgFlags, |
|
ChannelFlags: chanFlags, |
|
TimeLockDelta: uint16(f.cfg.DefaultRoutingPolicy.TimeLockDelta), |
|
|
|
// We use the HtlcMinimumMsat that the remote party required us |
|
// to use, as our ChannelUpdate will be used to carry HTLCs |
|
// towards them. |
|
HtlcMinimumMsat: fwdMinHTLC, |
|
HtlcMaximumMsat: fwdMaxHTLC, |
|
|
|
BaseFee: uint32(f.cfg.DefaultRoutingPolicy.BaseFee), |
|
FeeRate: uint32(f.cfg.DefaultRoutingPolicy.FeeRate), |
|
} |
|
|
|
// With the channel update announcement constructed, we'll generate a |
|
// signature that signs a double-sha digest of the announcement. |
|
// This'll serve to authenticate this announcement and any other future |
|
// updates we may send. |
|
chanUpdateMsg, err := chanUpdateAnn.DataToSign() |
|
if err != nil { |
|
return nil, err |
|
} |
|
sig, err := f.cfg.SignMessage(f.cfg.IDKey, chanUpdateMsg) |
|
if err != nil { |
|
return nil, errors.Errorf("unable to generate channel "+ |
|
"update announcement signature: %v", err) |
|
} |
|
chanUpdateAnn.Signature, err = lnwire.NewSigFromSignature(sig) |
|
if err != nil { |
|
return nil, errors.Errorf("unable to generate channel "+ |
|
"update announcement signature: %v", err) |
|
} |
|
|
|
// The channel existence proofs itself is currently announced in |
|
// distinct message. In order to properly authenticate this message, we |
|
// need two signatures: one under the identity public key used which |
|
// signs the message itself and another signature of the identity |
|
// public key under the funding key itself. |
|
// |
|
// TODO(roasbeef): use SignAnnouncement here instead? |
|
chanAnnMsg, err := chanAnn.DataToSign() |
|
if err != nil { |
|
return nil, err |
|
} |
|
nodeSig, err := f.cfg.SignMessage(f.cfg.IDKey, chanAnnMsg) |
|
if err != nil { |
|
return nil, errors.Errorf("unable to generate node "+ |
|
"signature for channel announcement: %v", err) |
|
} |
|
bitcoinSig, err := f.cfg.SignMessage(localFundingKey, chanAnnMsg) |
|
if err != nil { |
|
return nil, errors.Errorf("unable to generate bitcoin "+ |
|
"signature for node public key: %v", err) |
|
} |
|
|
|
// Finally, we'll generate the announcement proof which we'll use to |
|
// provide the other side with the necessary signatures required to |
|
// allow them to reconstruct the full channel announcement. |
|
proof := &lnwire.AnnounceSignatures{ |
|
ChannelID: chanID, |
|
ShortChannelID: shortChanID, |
|
} |
|
proof.NodeSignature, err = lnwire.NewSigFromSignature(nodeSig) |
|
if err != nil { |
|
return nil, err |
|
} |
|
proof.BitcoinSignature, err = lnwire.NewSigFromSignature(bitcoinSig) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return &chanAnnouncement{ |
|
chanAnn: chanAnn, |
|
chanUpdateAnn: chanUpdateAnn, |
|
chanProof: proof, |
|
}, nil |
|
} |
|
|
|
// announceChannel announces a newly created channel to the rest of the network |
|
// by crafting the two authenticated announcements required for the peers on |
|
// the network to recognize the legitimacy of the channel. The crafted |
|
// announcements are then sent to the channel router to handle broadcasting to |
|
// the network during its next trickle. |
|
// This method is synchronous and will return when all the network requests |
|
// finish, either successfully or with an error. |
|
func (f *fundingManager) announceChannel(localIDKey, remoteIDKey, localFundingKey, |
|
remoteFundingKey *btcec.PublicKey, shortChanID lnwire.ShortChannelID, |
|
chanID lnwire.ChannelID, fwdMinHTLC, fwdMaxHTLC lnwire.MilliSatoshi) error { |
|
|
|
// First, we'll create the batch of announcements to be sent upon |
|
// initial channel creation. This includes the channel announcement |
|
// itself, the channel update announcement, and our half of the channel |
|
// proof needed to fully authenticate the channel. |
|
ann, err := f.newChanAnnouncement(localIDKey, remoteIDKey, |
|
localFundingKey, remoteFundingKey, shortChanID, chanID, |
|
fwdMinHTLC, fwdMaxHTLC, |
|
) |
|
if err != nil { |
|
fndgLog.Errorf("can't generate channel announcement: %v", err) |
|
return err |
|
} |
|
|
|
// We only send the channel proof announcement and the node announcement |
|
// because addToRouterGraph previously send the ChannelAnnouncement and |
|
// the ChannelUpdate announcement messages. The channel proof and node |
|
// announcements are broadcast to the greater network. |
|
errChan := f.cfg.SendAnnouncement(ann.chanProof) |
|
select { |
|
case err := <-errChan: |
|
if err != nil { |
|
if routing.IsError(err, routing.ErrOutdated, |
|
routing.ErrIgnored) { |
|
fndgLog.Debugf("Router rejected "+ |
|
"AnnounceSignatures: %v", err) |
|
} else { |
|
fndgLog.Errorf("Unable to send channel "+ |
|
"proof: %v", err) |
|
return err |
|
} |
|
} |
|
|
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
// Now that the channel is announced to the network, we will also |
|
// obtain and send a node announcement. This is done since a node |
|
// announcement is only accepted after a channel is known for that |
|
// particular node, and this might be our first channel. |
|
nodeAnn, err := f.cfg.CurrentNodeAnnouncement() |
|
if err != nil { |
|
fndgLog.Errorf("can't generate node announcement: %v", err) |
|
return err |
|
} |
|
|
|
errChan = f.cfg.SendAnnouncement(&nodeAnn) |
|
select { |
|
case err := <-errChan: |
|
if err != nil { |
|
if routing.IsError(err, routing.ErrOutdated, |
|
routing.ErrIgnored) { |
|
fndgLog.Debugf("Router rejected "+ |
|
"NodeAnnouncement: %v", err) |
|
} else { |
|
fndgLog.Errorf("Unable to send node "+ |
|
"announcement: %v", err) |
|
return err |
|
} |
|
} |
|
|
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// initFundingWorkflow sends a message to the funding manager instructing it |
|
// to initiate a single funder workflow with the source peer. |
|
// TODO(roasbeef): re-visit blocking nature.. |
|
func (f *fundingManager) initFundingWorkflow(peer lnpeer.Peer, req *openChanReq) { |
|
f.fundingRequests <- &initFundingMsg{ |
|
peer: peer, |
|
openChanReq: req, |
|
} |
|
} |
|
|
|
// handleInitFundingMsg creates a channel reservation within the daemon's |
|
// wallet, then sends a funding request to the remote peer kicking off the |
|
// funding workflow. |
|
func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { |
|
var ( |
|
peerKey = msg.peer.IdentityKey() |
|
localAmt = msg.localFundingAmt |
|
remoteAmt = msg.remoteFundingAmt |
|
capacity = localAmt + remoteAmt |
|
minHtlc = msg.minHtlc |
|
remoteCsvDelay = msg.remoteCsvDelay |
|
) |
|
|
|
// We'll determine our dust limit depending on which chain is active. |
|
var ourDustLimit btcutil.Amount |
|
switch registeredChains.PrimaryChain() { |
|
case bitcoinChain: |
|
ourDustLimit = lnwallet.DefaultDustLimit() |
|
case litecoinChain: |
|
ourDustLimit = defaultLitecoinDustLimit |
|
} |
|
|
|
fndgLog.Infof("Initiating fundingRequest(localAmt=%v, remoteAmt=%v, "+ |
|
"capacity=%v, chainhash=%v, peer=%x, dustLimit=%v, min_confs=%v)", |
|
localAmt, msg.pushAmt, capacity, msg.chainHash, |
|
peerKey.SerializeCompressed(), ourDustLimit, msg.minConfs) |
|
|
|
// First, we'll query the fee estimator for a fee that should get the |
|
// commitment transaction confirmed by the next few blocks (conf target |
|
// of 3). We target the near blocks here to ensure that we'll be able |
|
// to execute a timely unilateral channel closure if needed. |
|
commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3) |
|
if err != nil { |
|
msg.err <- err |
|
return |
|
} |
|
|
|
// We set the channel flags to indicate whether we want this channel to |
|
// be announced to the network. |
|
var channelFlags lnwire.FundingFlag |
|
if !msg.openChanReq.private { |
|
// This channel will be announced. |
|
channelFlags = lnwire.FFAnnounceChannel |
|
} |
|
|
|
// Initialize a funding reservation with the local wallet. If the |
|
// wallet doesn't have enough funds to commit to this channel, then the |
|
// request will fail, and be aborted. |
|
req := &lnwallet.InitFundingReserveMsg{ |
|
ChainHash: &msg.chainHash, |
|
NodeID: peerKey, |
|
NodeAddr: msg.peer.Address(), |
|
FundingAmount: localAmt, |
|
Capacity: capacity, |
|
CommitFeePerKw: commitFeePerKw, |
|
FundingFeePerKw: msg.fundingFeePerKw, |
|
PushMSat: msg.pushAmt, |
|
Flags: channelFlags, |
|
MinConfs: msg.minConfs, |
|
} |
|
|
|
reservation, err := f.cfg.Wallet.InitChannelReservation(req) |
|
if err != nil { |
|
msg.err <- err |
|
return |
|
} |
|
|
|
// Obtain a new pending channel ID which is used to track this |
|
// reservation throughout its lifetime. |
|
chanID := f.nextPendingChanID() |
|
|
|
fndgLog.Infof("Target commit tx sat/kw for pendingID(%x): %v", chanID, |
|
int64(commitFeePerKw)) |
|
|
|
// If the remote CSV delay was not set in the open channel request, |
|
// we'll use the RequiredRemoteDelay closure to compute the delay we |
|
// require given the total amount of funds within the channel. |
|
if remoteCsvDelay == 0 { |
|
remoteCsvDelay = f.cfg.RequiredRemoteDelay(capacity) |
|
} |
|
|
|
// If no minimum HTLC value was specified, use the default one. |
|
if minHtlc == 0 { |
|
minHtlc = f.cfg.DefaultRoutingPolicy.MinHTLC |
|
} |
|
|
|
// If a pending channel map for this peer isn't already created, then |
|
// we create one, ultimately allowing us to track this pending |
|
// reservation within the target peer. |
|
peerIDKey := newSerializedKey(peerKey) |
|
f.resMtx.Lock() |
|
if _, ok := f.activeReservations[peerIDKey]; !ok { |
|
f.activeReservations[peerIDKey] = make(pendingChannels) |
|
} |
|
|
|
resCtx := &reservationWithCtx{ |
|
chanAmt: capacity, |
|
remoteCsvDelay: remoteCsvDelay, |
|
remoteMinHtlc: minHtlc, |
|
reservation: reservation, |
|
peer: msg.peer, |
|
updates: msg.updates, |
|
err: msg.err, |
|
} |
|
f.activeReservations[peerIDKey][chanID] = resCtx |
|
f.resMtx.Unlock() |
|
|
|
// Update the timestamp once the initFundingMsg has been handled. |
|
defer resCtx.updateTimestamp() |
|
|
|
// Once the reservation has been created, and indexed, queue a funding |
|
// request to the remote peer, kicking off the funding workflow. |
|
ourContribution := reservation.OurContribution() |
|
|
|
// Finally, we'll use the current value of the channels and our default |
|
// policy to determine of required commitment constraints for the |
|
// remote party. |
|
chanReserve := f.cfg.RequiredRemoteChanReserve(capacity, ourDustLimit) |
|
maxValue := f.cfg.RequiredRemoteMaxValue(capacity) |
|
maxHtlcs := f.cfg.RequiredRemoteMaxHTLCs(capacity) |
|
|
|
fndgLog.Infof("Starting funding workflow with %v for pendingID(%x)", |
|
msg.peer.Address(), chanID) |
|
|
|
fundingOpen := lnwire.OpenChannel{ |
|
ChainHash: *f.cfg.Wallet.Cfg.NetParams.GenesisHash, |
|
PendingChannelID: chanID, |
|
FundingAmount: capacity, |
|
PushAmount: msg.pushAmt, |
|
DustLimit: ourContribution.DustLimit, |
|
MaxValueInFlight: maxValue, |
|
ChannelReserve: chanReserve, |
|
HtlcMinimum: minHtlc, |
|
FeePerKiloWeight: uint32(commitFeePerKw), |
|
CsvDelay: remoteCsvDelay, |
|
MaxAcceptedHTLCs: maxHtlcs, |
|
FundingKey: ourContribution.MultiSigKey.PubKey, |
|
RevocationPoint: ourContribution.RevocationBasePoint.PubKey, |
|
PaymentPoint: ourContribution.PaymentBasePoint.PubKey, |
|
HtlcPoint: ourContribution.HtlcBasePoint.PubKey, |
|
DelayedPaymentPoint: ourContribution.DelayBasePoint.PubKey, |
|
FirstCommitmentPoint: ourContribution.FirstCommitmentPoint, |
|
ChannelFlags: channelFlags, |
|
} |
|
if err := msg.peer.SendMessage(false, &fundingOpen); err != nil { |
|
e := fmt.Errorf("Unable to send funding request message: %v", |
|
err) |
|
fndgLog.Errorf(e.Error()) |
|
|
|
// Since we were unable to send the initial message to the peer |
|
// and start the funding flow, we'll cancel this reservation. |
|
if _, err := f.cancelReservationCtx(peerKey, chanID); err != nil { |
|
fndgLog.Errorf("unable to cancel reservation: %v", err) |
|
} |
|
|
|
msg.err <- e |
|
return |
|
} |
|
} |
|
|
|
// waitUntilChannelOpen is designed to prevent other lnd subsystems from |
|
// sending new update messages to a channel before the channel is fully |
|
// opened. |
|
func (f *fundingManager) waitUntilChannelOpen(targetChan lnwire.ChannelID, |
|
quit <-chan struct{}) error { |
|
|
|
f.barrierMtx.RLock() |
|
barrier, ok := f.newChanBarriers[targetChan] |
|
f.barrierMtx.RUnlock() |
|
if ok { |
|
fndgLog.Tracef("waiting for chan barrier signal for ChanID(%v)", |
|
targetChan) |
|
|
|
select { |
|
case <-barrier: |
|
case <-quit: |
|
return ErrFundingManagerShuttingDown |
|
case <-f.quit: |
|
return ErrFundingManagerShuttingDown |
|
} |
|
|
|
fndgLog.Tracef("barrier for ChanID(%v) closed", targetChan) |
|
return nil |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// processFundingError sends a message to the fundingManager allowing it to |
|
// process the occurred generic error. |
|
func (f *fundingManager) processFundingError(err *lnwire.Error, |
|
peerKey *btcec.PublicKey) { |
|
|
|
select { |
|
case f.fundingMsgs <- &fundingErrorMsg{err, peerKey}: |
|
case <-f.quit: |
|
return |
|
} |
|
} |
|
|
|
// handleErrorMsg processes the error which was received from remote peer, |
|
// depending on the type of error we should do different clean up steps and |
|
// inform the user about it. |
|
func (f *fundingManager) handleErrorMsg(fmsg *fundingErrorMsg) { |
|
protocolErr := fmsg.err |
|
|
|
chanID := fmsg.err.ChanID |
|
|
|
// First, we'll attempt to retrieve and cancel the funding workflow |
|
// that this error was tied to. If we're unable to do so, then we'll |
|
// exit early as this was an unwarranted error. |
|
resCtx, err := f.cancelReservationCtx(fmsg.peerKey, chanID) |
|
if err != nil { |
|
fndgLog.Warnf("Received error for non-existent funding "+ |
|
"flow: %v (%v)", err, spew.Sdump(protocolErr)) |
|
return |
|
} |
|
|
|
// If we did indeed find the funding workflow, then we'll return the |
|
// error back to the caller (if any), and cancel the workflow itself. |
|
lnErr := lnwire.ErrorCode(protocolErr.Data[0]) |
|
fndgLog.Errorf("Received funding error from %x: %v", |
|
fmsg.peerKey.SerializeCompressed(), string(protocolErr.Data), |
|
) |
|
|
|
// If this isn't a simple error code, then we'll display the entire |
|
// thing. |
|
if len(protocolErr.Data) > 1 { |
|
err = grpc.Errorf( |
|
lnErr.ToGrpcCode(), string(protocolErr.Data), |
|
) |
|
} else { |
|
// Otherwise, we'll attempt to display just the error code |
|
// itself. |
|
err = grpc.Errorf( |
|
lnErr.ToGrpcCode(), lnErr.String(), |
|
) |
|
} |
|
resCtx.err <- err |
|
} |
|
|
|
// pruneZombieReservations loops through all pending reservations and fails the |
|
// funding flow for any reservations that have not been updated since the |
|
// ReservationTimeout and are not locked waiting for the funding transaction. |
|
func (f *fundingManager) pruneZombieReservations() { |
|
zombieReservations := make(pendingChannels) |
|
|
|
f.resMtx.RLock() |
|
for _, pendingReservations := range f.activeReservations { |
|
for pendingChanID, resCtx := range pendingReservations { |
|
if resCtx.isLocked() { |
|
continue |
|
} |
|
|
|
if time.Since(resCtx.lastUpdated) > f.cfg.ReservationTimeout { |
|
zombieReservations[pendingChanID] = resCtx |
|
} |
|
} |
|
} |
|
f.resMtx.RUnlock() |
|
|
|
for pendingChanID, resCtx := range zombieReservations { |
|
err := fmt.Errorf("reservation timed out waiting for peer "+ |
|
"(peerID:%v, chanID:%x)", resCtx.peer.IdentityKey(), |
|
pendingChanID[:]) |
|
fndgLog.Warnf(err.Error()) |
|
f.failFundingFlow(resCtx.peer, pendingChanID, err) |
|
} |
|
} |
|
|
|
// cancelReservationCtx does all needed work in order to securely cancel the |
|
// reservation. |
|
func (f *fundingManager) cancelReservationCtx(peerKey *btcec.PublicKey, |
|
pendingChanID [32]byte) (*reservationWithCtx, error) { |
|
|
|
fndgLog.Infof("Cancelling funding reservation for node_key=%x, "+ |
|
"chan_id=%x", peerKey.SerializeCompressed(), pendingChanID[:]) |
|
|
|
peerIDKey := newSerializedKey(peerKey) |
|
f.resMtx.Lock() |
|
defer f.resMtx.Unlock() |
|
|
|
nodeReservations, ok := f.activeReservations[peerIDKey] |
|
if !ok { |
|
// No reservations for this node. |
|
return nil, errors.Errorf("no active reservations for peer(%x)", |
|
peerIDKey[:]) |
|
} |
|
|
|
ctx, ok := nodeReservations[pendingChanID] |
|
if !ok { |
|
return nil, errors.Errorf("unknown channel (id: %x) for "+ |
|
"peer(%x)", pendingChanID[:], peerIDKey[:]) |
|
} |
|
|
|
if err := ctx.reservation.Cancel(); err != nil { |
|
return nil, errors.Errorf("unable to cancel reservation: %v", |
|
err) |
|
} |
|
|
|
delete(nodeReservations, pendingChanID) |
|
|
|
// If this was the last active reservation for this peer, delete the |
|
// peer's entry altogether. |
|
if len(nodeReservations) == 0 { |
|
delete(f.activeReservations, peerIDKey) |
|
} |
|
return ctx, nil |
|
} |
|
|
|
// 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, |
|
pendingChanID [32]byte) { |
|
|
|
// TODO(roasbeef): possibly cancel funding barrier in peer's |
|
// channelManager? |
|
peerIDKey := newSerializedKey(peerKey) |
|
f.resMtx.Lock() |
|
defer f.resMtx.Unlock() |
|
|
|
nodeReservations, ok := f.activeReservations[peerIDKey] |
|
if !ok { |
|
// No reservations for this node. |
|
return |
|
} |
|
delete(nodeReservations, pendingChanID) |
|
|
|
// If this was the last active reservation for this peer, delete the |
|
// peer's entry altogether. |
|
if len(nodeReservations) == 0 { |
|
delete(f.activeReservations, peerIDKey) |
|
} |
|
} |
|
|
|
// getReservationCtx returns the reservation context for a particular pending |
|
// channel ID for a target peer. |
|
func (f *fundingManager) getReservationCtx(peerKey *btcec.PublicKey, |
|
pendingChanID [32]byte) (*reservationWithCtx, error) { |
|
|
|
peerIDKey := newSerializedKey(peerKey) |
|
f.resMtx.RLock() |
|
resCtx, ok := f.activeReservations[peerIDKey][pendingChanID] |
|
f.resMtx.RUnlock() |
|
|
|
if !ok { |
|
return nil, errors.Errorf("unknown channel (id: %x) for "+ |
|
"peer(%x)", pendingChanID[:], peerIDKey[:]) |
|
} |
|
|
|
return resCtx, nil |
|
} |
|
|
|
// IsPendingChannel returns a boolean indicating whether the channel identified |
|
// by the pendingChanID and given peer is pending, meaning it is in the process |
|
// of being funded. After the funding transaction has been confirmed, the |
|
// channel will receive a new, permanent channel ID, and will no longer be |
|
// considered pending. |
|
func (f *fundingManager) IsPendingChannel(pendingChanID [32]byte, |
|
peerKey *btcec.PublicKey) bool { |
|
|
|
peerIDKey := newSerializedKey(peerKey) |
|
f.resMtx.RLock() |
|
_, ok := f.activeReservations[peerIDKey][pendingChanID] |
|
f.resMtx.RUnlock() |
|
|
|
return ok |
|
} |
|
|
|
func copyPubKey(pub *btcec.PublicKey) *btcec.PublicKey { |
|
return &btcec.PublicKey{ |
|
Curve: btcec.S256(), |
|
X: pub.X, |
|
Y: pub.Y, |
|
} |
|
} |
|
|
|
// saveChannelOpeningState saves the channelOpeningState for the provided |
|
// chanPoint to the channelOpeningStateBucket. |
|
func (f *fundingManager) saveChannelOpeningState(chanPoint *wire.OutPoint, |
|
state channelOpeningState, shortChanID *lnwire.ShortChannelID) error { |
|
return f.cfg.Wallet.Cfg.Database.Update(func(tx *bbolt.Tx) error { |
|
|
|
bucket, err := tx.CreateBucketIfNotExists(channelOpeningStateBucket) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var outpointBytes bytes.Buffer |
|
if err = writeOutpoint(&outpointBytes, chanPoint); err != nil { |
|
return err |
|
} |
|
|
|
// Save state and the uint64 representation of the shortChanID |
|
// for later use. |
|
scratch := make([]byte, 10) |
|
byteOrder.PutUint16(scratch[:2], uint16(state)) |
|
byteOrder.PutUint64(scratch[2:], shortChanID.ToUint64()) |
|
|
|
return bucket.Put(outpointBytes.Bytes(), scratch) |
|
}) |
|
} |
|
|
|
// getChannelOpeningState fetches the channelOpeningState for the provided |
|
// chanPoint from the database, or returns ErrChannelNotFound if the channel |
|
// is not found. |
|
func (f *fundingManager) getChannelOpeningState(chanPoint *wire.OutPoint) ( |
|
channelOpeningState, *lnwire.ShortChannelID, error) { |
|
|
|
var state channelOpeningState |
|
var shortChanID lnwire.ShortChannelID |
|
err := f.cfg.Wallet.Cfg.Database.View(func(tx *bbolt.Tx) error { |
|
|
|
bucket := tx.Bucket(channelOpeningStateBucket) |
|
if bucket == nil { |
|
// If the bucket does not exist, it means we never added |
|
// a channel to the db, so return ErrChannelNotFound. |
|
return ErrChannelNotFound |
|
} |
|
|
|
var outpointBytes bytes.Buffer |
|
if err := writeOutpoint(&outpointBytes, chanPoint); err != nil { |
|
return err |
|
} |
|
|
|
value := bucket.Get(outpointBytes.Bytes()) |
|
if value == nil { |
|
return ErrChannelNotFound |
|
} |
|
|
|
state = channelOpeningState(byteOrder.Uint16(value[:2])) |
|
shortChanID = lnwire.NewShortChanIDFromInt(byteOrder.Uint64(value[2:])) |
|
return nil |
|
}) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
|
|
return state, &shortChanID, nil |
|
} |
|
|
|
// deleteChannelOpeningState removes any state for chanPoint from the database. |
|
func (f *fundingManager) deleteChannelOpeningState(chanPoint *wire.OutPoint) error { |
|
return f.cfg.Wallet.Cfg.Database.Update(func(tx *bbolt.Tx) error { |
|
bucket := tx.Bucket(channelOpeningStateBucket) |
|
if bucket == nil { |
|
return fmt.Errorf("Bucket not found") |
|
} |
|
|
|
var outpointBytes bytes.Buffer |
|
if err := writeOutpoint(&outpointBytes, chanPoint); err != nil { |
|
return err |
|
} |
|
|
|
return bucket.Delete(outpointBytes.Bytes()) |
|
}) |
|
}
|
|
|