From bc176b5aa3d16ccf444680feb870c67ae67d4787 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Wed, 13 Nov 2019 21:00:30 -0800 Subject: [PATCH] rpc+lnwallet: implement new FundingStateStep RPC method In this commit, we implement the currently defined transition methods for the new `FundingStateStep` method. At this point, we're now able to serve the "responder" of the externally initiated channel funding flow by being able to register and cancel a funding flow according to its expected pending channel ID. --- lnwallet/chanfunding/canned_assembler.go | 16 +++--- lnwallet/wallet.go | 24 ++++++++- rpcserver.go | 66 ++++++++++++++++++++++-- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/lnwallet/chanfunding/canned_assembler.go b/lnwallet/chanfunding/canned_assembler.go index 10fe811b..36ebba15 100644 --- a/lnwallet/chanfunding/canned_assembler.go +++ b/lnwallet/chanfunding/canned_assembler.go @@ -154,15 +154,9 @@ func NewCannedAssembler(chanPoint wire.OutPoint, fundingAmt btcutil.Amount, // // NOTE: This method satisfies the chanfunding.Assembler interface. func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) { - switch { - // A simple sanity check to ensure the provision request matches the - // re-made shim intent. - case req.LocalAmt != c.fundingAmt: - return nil, fmt.Errorf("intent doesn't match canned assembler") - // We'll exit out if this field is set as the funding transaction has // already been assembled, so we don't influence coin selection.. - case req.SubtractFees: + if req.SubtractFees { return nil, fmt.Errorf("SubtractFees ignored, funding " + "transaction is frozen") } @@ -179,6 +173,14 @@ func (c *CannedAssembler) ProvisionChannel(req *Request) (Intent, error) { intent.remoteFundingAmt = c.fundingAmt } + // A simple sanity check to ensure the provisioned request matches the + // re-made shim intent. + if req.LocalAmt+req.RemoteAmt != c.fundingAmt { + return nil, fmt.Errorf("intent doesn't match canned "+ + "assembler: local_amt=%v, remote_amt=%v, funding_amt=%v", + req.LocalAmt, req.RemoteAmt, c.fundingAmt) + } + return intent, nil } diff --git a/lnwallet/wallet.go b/lnwallet/wallet.go index 855260a5..a6b9b875 100644 --- a/lnwallet/wallet.go +++ b/lnwallet/wallet.go @@ -450,8 +450,30 @@ func (l *LightningWallet) RegisterFundingIntent(expectedID [32]byte, shimIntent chanfunding.Intent) error { l.intentMtx.Lock() + defer l.intentMtx.Unlock() + + if _, ok := l.fundingIntents[expectedID]; ok { + return fmt.Errorf("pendingChanID(%x) already has intent "+ + "registered", expectedID[:]) + } + l.fundingIntents[expectedID] = shimIntent - l.intentMtx.Unlock() + + return nil +} + +// CancelFundingIntent allows a caller to cancel a previously registered +// funding intent. If no intent was found, then an error will be returned. +func (l *LightningWallet) CancelFundingIntent(pid [32]byte) error { + l.intentMtx.Lock() + defer l.intentMtx.Unlock() + + if _, ok := l.fundingIntents[pid]; !ok { + return fmt.Errorf("no funding intent found for "+ + "pendingChannelID(%x)", pid[:]) + } + + delete(l.fundingIntents, pid) return nil } diff --git a/rpcserver.go b/rpcserver.go index 9dcec230..d16ad790 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -5814,8 +5814,68 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context, // ID, for which we need to use specific parameters. Alternatively, this can // be used to interactively drive PSBT signing for funding for partially // complete funding transactions. -func (r *rpcServer) FundingStateStep(context.Context, - *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) { +func (r *rpcServer) FundingStateStep(ctx context.Context, + in *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) { - return nil, fmt.Errorf("not implemented") + switch { + + // If this is a message to register a new shim that is an external + // channel point, then we'll contact the wallet to register this new + // shim. A user will use this method to register a new channel funding + // workflow which has already been partially negotiated outside of the + // core protocol. + case in.GetShimRegister() != nil && + in.GetShimRegister().GetChanPointShim() != nil: + + rpcShimIntent := in.GetShimRegister().GetChanPointShim() + + // Using the rpc shim as a template, we'll construct a new + // chanfunding.Assembler that is able to express proper + // formulation of this expected channel. + shimAssembler, err := newFundingShimAssembler( + rpcShimIntent, false, r.server.cc.keyRing, + ) + if err != nil { + return nil, err + } + req := &chanfunding.Request{ + RemoteAmt: btcutil.Amount(rpcShimIntent.Amt), + } + shimIntent, err := shimAssembler.ProvisionChannel(req) + if err != nil { + return nil, err + } + + // Once we have the intent, we'll register it with the wallet. + // Once we receive an incoming funding request that uses this + // pending channel ID, then this shim will be dispatched in + // place of our regular funding workflow. + var pendingChanID [32]byte + copy(pendingChanID[:], rpcShimIntent.PendingChanId) + err = r.server.cc.wallet.RegisterFundingIntent( + pendingChanID, shimIntent, + ) + if err != nil { + return nil, err + } + + // If this is a transition to cancel an existing shim, then we'll pass + // this message along to the wallet. + case in.GetShimCancel() != nil: + pid := in.GetShimCancel().PendingChanId + + var pendingChanID [32]byte + copy(pendingChanID[:], pid) + + err := r.server.cc.wallet.CancelFundingIntent(pendingChanID) + if err != nil { + return nil, err + } + } + + // TODO(roasbeef): extend PendingChannels to also show shims + + // TODO(roasbeef): return resulting state? also add a method to query + // current state? + return &lnrpc.FundingStateStepResp{}, nil }