diff --git a/fundingmanager.go b/fundingmanager.go index b11bf938..09d5807b 100644 --- a/fundingmanager.go +++ b/fundingmanager.go @@ -99,6 +99,8 @@ var ( // script is set for a peer that does not support the feature bit. errUpfrontShutdownScriptNotSupported = errors.New("peer does not support" + "option upfront shutdown script") + + zeroID [32]byte ) // reservationWithCtx encapsulates a pending channel reservation. This wrapper @@ -840,8 +842,8 @@ func (f *fundingManager) advanceFundingState(channel *channeldb.OpenChannel, // are still steps left of the setup procedure. We continue the // procedure where we left off. err = f.stateStep( - channel, lnChannel, shortChanID, channelState, - updateChan, + channel, lnChannel, shortChanID, pendingChanID, + channelState, updateChan, ) if err != nil { fndgLog.Errorf("Unable to advance state(%v): %v", @@ -857,7 +859,8 @@ func (f *fundingManager) advanceFundingState(channel *channeldb.OpenChannel, // updateChan can be set non-nil to get OpenStatusUpdates. func (f *fundingManager) stateStep(channel *channeldb.OpenChannel, lnChannel *lnwallet.LightningChannel, - shortChanID *lnwire.ShortChannelID, channelState channelOpeningState, + shortChanID *lnwire.ShortChannelID, pendingChanID [32]byte, + channelState channelOpeningState, updateChan chan<- *lnrpc.OpenStatusUpdate) error { chanID := lnwire.NewChanIDFromOutPoint(&channel.FundingOutpoint) @@ -936,6 +939,7 @@ func (f *fundingManager) stateStep(channel *channeldb.OpenChannel, ChannelPoint: cp, }, }, + PendingChanId: pendingChanID[:], } select { @@ -1816,6 +1820,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { // 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{ @@ -1825,6 +1830,7 @@ func (f *fundingManager) handleFundingSigned(fmsg *fundingSignedMsg) { OutputIndex: fundingPoint.Index, }, }, + PendingChanId: pendingChanID[:], } select { @@ -2853,9 +2859,23 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { channelFlags = lnwire.FFAnnounceChannel } - // Obtain a new pending channel ID which is used to track this - // reservation throughout its lifetime. - chanID := f.nextPendingChanID() + // If the caller specified their own channel ID, then we'll use that. + // Otherwise we'll generate a fresh one as normal. This will be used + // to track this reservation throughout its lifetime. + var chanID [32]byte + if msg.pendingChanID == zeroID { + chanID = f.nextPendingChanID() + } else { + // If the user specified their own pending channel ID, then + // we'll ensure it doesn't collide with any existing pending + // channel ID. + chanID = msg.pendingChanID + if _, err := f.getReservationCtx(peerKey, chanID); err == nil { + msg.err <- fmt.Errorf("pendingChannelID(%x) "+ + "already present", chanID[:]) + return + } + } // Initialize a funding reservation with the local wallet. If the // wallet doesn't have enough funds to commit to this channel, then the @@ -2886,6 +2906,7 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) { Flags: channelFlags, MinConfs: msg.minConfs, Tweakless: tweaklessCommitment, + ChanFunder: msg.chanFunder, } reservation, err := f.cfg.Wallet.InitChannelReservation(req) diff --git a/rpcserver.go b/rpcserver.go index b7b564f9..9dcec230 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -40,6 +40,7 @@ import ( "github.com/lightningnetwork/lnd/htlcswitch" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/invoices" + "github.com/lightningnetwork/lnd/keychain" "github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc" @@ -47,6 +48,7 @@ import ( "github.com/lightningnetwork/lnd/lntypes" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/macaroons" "github.com/lightningnetwork/lnd/monitoring" @@ -1454,6 +1456,88 @@ func extractOpenChannelMinConfs(in *lnrpc.OpenChannelRequest) (int32, error) { } } +// newFundingShimAssembler returns a new fully populated +// chanfunding.CannedAssembler using a FundingShim obtained from an RPC caller. +func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, + initiator bool, keyRing keychain.KeyRing) (chanfunding.Assembler, error) { + + // Perform some basic sanity checks to ensure that all the expected + // fields are populated. + switch { + case chanPointShim.RemoteKey == nil: + return nil, fmt.Errorf("remote key not set") + + case chanPointShim.LocalKey == nil: + return nil, fmt.Errorf("local key desc not set") + + case chanPointShim.LocalKey.RawKeyBytes == nil: + return nil, fmt.Errorf("local raw key bytes not set") + + case chanPointShim.LocalKey.KeyLoc == nil: + return nil, fmt.Errorf("local key loc not set") + + case chanPointShim.ChanPoint == nil: + return nil, fmt.Errorf("chan point not set") + + case len(chanPointShim.PendingChanId) != 32: + return nil, fmt.Errorf("pending chan ID not set") + } + + // First, we'll map the RPC's channel point to one we can actually use. + index := chanPointShim.ChanPoint.OutputIndex + txid, err := GetChanPointFundingTxid(chanPointShim.ChanPoint) + if err != nil { + return nil, err + } + chanPoint := wire.NewOutPoint(txid, index) + + // Next we'll parse out the remote party's funding key, as well as our + // full key descriptor. + remoteKey, err := btcec.ParsePubKey( + chanPointShim.RemoteKey, btcec.S256(), + ) + if err != nil { + return nil, err + } + + shimKeyDesc := chanPointShim.LocalKey + localKey, err := btcec.ParsePubKey( + shimKeyDesc.RawKeyBytes, btcec.S256(), + ) + if err != nil { + return nil, err + } + localKeyDesc := keychain.KeyDescriptor{ + PubKey: localKey, + KeyLocator: keychain.KeyLocator{ + Family: keychain.KeyFamily( + shimKeyDesc.KeyLoc.KeyFamily, + ), + Index: uint32(shimKeyDesc.KeyLoc.KeyIndex), + }, + } + + // Verify that if we re-derive this key according to the passed + // KeyLocator, that we get the exact same key back. Otherwise, we may + // end up in a situation where we aren't able to actually sign for this + // newly created channel. + derivedKey, err := keyRing.DeriveKey(localKeyDesc.KeyLocator) + if err != nil { + return nil, err + } + if !derivedKey.PubKey.IsEqual(localKey) { + return nil, fmt.Errorf("KeyLocator does not match attached " + + "raw pubkey") + } + + // With all the parts assembled, we can now make the canned assembler + // to pass into the wallet. + return chanfunding.NewCannedAssembler( + *chanPoint, btcutil.Amount(chanPointShim.Amt), + &localKeyDesc, remoteKey, initiator, + ), nil +} + // OpenChannel attempts to open a singly funded channel specified in the // request to a remote peer. func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, @@ -1570,6 +1654,28 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest, shutdownScript: script, } + // If the user has provided a shim, then we'll now augment the based + // open channel request with this additional logic. + if in.FundingShim != nil { + // If we have a chan point shim, then this means the funding + // transaction was crafted externally. In this case we only + // need to hand a channel point down into the wallet. + if in.FundingShim.GetChanPointShim() != nil { + chanPointShim := in.FundingShim.GetChanPointShim() + + // Map the channel point shim into a new + // chanfunding.CannedAssembler that the wallet will use + // to obtain the channel point details. + copy(req.pendingChanID[:], chanPointShim.PendingChanId) + req.chanFunder, err = newFundingShimAssembler( + chanPointShim, true, r.server.cc.keyRing, + ) + if err != nil { + return err + } + } + } + updateChan, errChan := r.server.OpenChannel(req) var outpoint wire.OutPoint diff --git a/server.go b/server.go index dd97b2be..076e4d7b 100644 --- a/server.go +++ b/server.go @@ -47,6 +47,7 @@ import ( "github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" + "github.com/lightningnetwork/lnd/lnwallet/chanfunding" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/nat" "github.com/lightningnetwork/lnd/netann" @@ -3108,6 +3109,17 @@ type openChanReq struct { // TODO(roasbeef): add ability to specify channel constraints as well + // chanFunder is an optional channel funder that allows the caller to + // control exactly how the channel funding is carried out. If not + // specified, then the default chanfunding.WalletAssembler will be + // used. + chanFunder chanfunding.Assembler + + // pendingChanID is not all zeroes (the default value), then this will + // be the pending channel ID used for the funding flow within the wire + // protocol. + pendingChanID [32]byte + updates chan *lnrpc.OpenStatusUpdate err chan error }