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.
This commit is contained in:
Olaoluwa Osuntokun 2019-11-13 20:58:54 -08:00
parent 3eed38d602
commit 91bd56dbd1
No known key found for this signature in database
GPG Key ID: BC13F65E2DC84465
3 changed files with 145 additions and 6 deletions

View File

@ -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)

View File

@ -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

View File

@ -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
}