From 91bd56dbd11851f4e233f52a58e3f35cf5758ac3 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 13 Nov 2019 20:58:54 -0800 Subject: [PATCH] funding+rpc: expand specified chan point shim into a canned assembler In this commit, we update the `OpenChannel` method to observe the new `funding_shim` field in the main open channel request. If this is specified, and is a channel point shim, then we'll create a custom `chanfunding.Assembler` for the wallet to use in place of the regular funding workflow. With this commit, the "initiator" of an external funding flow can now delegate the remainder of the channel funding workflow to lnd. --- fundingmanager.go | 33 ++++++++++++--- rpcserver.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++ server.go | 12 ++++++ 3 files changed, 145 insertions(+), 6 deletions(-) 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 }