rpcserver: implement PSBT funding flow
A PSBT funding flow consists of multiple steps. We add new RPC messages that can trigger the underlying state machine to transition to a new state. We also add new response messages that tell the API user what the current state is.
This commit is contained in:
1 file changed
+127
-7
+127
-7
@@ -19,10 +19,12 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain"
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
"github.com/btcsuite/btcutil/psbt"
|
||||||
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
"github.com/btcsuite/btcwallet/wallet/txauthor"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||||
@@ -1556,6 +1558,51 @@ func newFundingShimAssembler(chanPointShim *lnrpc.ChanPointShim, initiator bool,
|
|||||||
), nil
|
), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newFundingShimAssembler returns a new fully populated
|
||||||
|
// chanfunding.PsbtAssembler using a FundingShim obtained from an RPC caller.
|
||||||
|
func newPsbtAssembler(req *lnrpc.OpenChannelRequest, normalizedMinConfs int32,
|
||||||
|
psbtShim *lnrpc.PsbtShim, netParams *chaincfg.Params) (
|
||||||
|
chanfunding.Assembler, error) {
|
||||||
|
|
||||||
|
var (
|
||||||
|
packet *psbt.Packet
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
// Perform some basic sanity checks to ensure that all the expected
|
||||||
|
// fields are populated and none of the incompatible fields are.
|
||||||
|
if len(psbtShim.PendingChanId) != 32 {
|
||||||
|
return nil, fmt.Errorf("pending chan ID not set")
|
||||||
|
}
|
||||||
|
if normalizedMinConfs != 1 {
|
||||||
|
return nil, fmt.Errorf("setting non-default values for " +
|
||||||
|
"minimum confirmation is not supported for PSBT " +
|
||||||
|
"funding")
|
||||||
|
}
|
||||||
|
if req.SatPerByte != 0 || req.TargetConf != 0 {
|
||||||
|
return nil, fmt.Errorf("specifying fee estimation parameters " +
|
||||||
|
"is not supported for PSBT funding")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The base PSBT is optional. But if it's set, it has to be a valid,
|
||||||
|
// binary serialized PSBT.
|
||||||
|
if len(psbtShim.BasePsbt) > 0 {
|
||||||
|
packet, err = psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader(psbtShim.BasePsbt), false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing base PSBT: %v",
|
||||||
|
err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// With all the parts assembled, we can now make the canned assembler
|
||||||
|
// to pass into the wallet.
|
||||||
|
return chanfunding.NewPsbtAssembler(
|
||||||
|
btcutil.Amount(req.LocalFundingAmount), packet, netParams,
|
||||||
|
), nil
|
||||||
|
}
|
||||||
|
|
||||||
// OpenChannel attempts to open a singly funded channel specified in the
|
// OpenChannel attempts to open a singly funded channel specified in the
|
||||||
// request to a remote peer.
|
// request to a remote peer.
|
||||||
func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
||||||
@@ -1675,10 +1722,11 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
|||||||
// If the user has provided a shim, then we'll now augment the based
|
// If the user has provided a shim, then we'll now augment the based
|
||||||
// open channel request with this additional logic.
|
// open channel request with this additional logic.
|
||||||
if in.FundingShim != nil {
|
if in.FundingShim != nil {
|
||||||
|
switch {
|
||||||
// If we have a chan point shim, then this means the funding
|
// If we have a chan point shim, then this means the funding
|
||||||
// transaction was crafted externally. In this case we only
|
// transaction was crafted externally. In this case we only
|
||||||
// need to hand a channel point down into the wallet.
|
// need to hand a channel point down into the wallet.
|
||||||
if in.FundingShim.GetChanPointShim() != nil {
|
case in.FundingShim.GetChanPointShim() != nil:
|
||||||
chanPointShim := in.FundingShim.GetChanPointShim()
|
chanPointShim := in.FundingShim.GetChanPointShim()
|
||||||
|
|
||||||
// Map the channel point shim into a new
|
// Map the channel point shim into a new
|
||||||
@@ -1691,6 +1739,25 @@ func (r *rpcServer) OpenChannel(in *lnrpc.OpenChannelRequest,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we have a PSBT shim, then this means the funding
|
||||||
|
// transaction will be crafted outside of the wallet, once the
|
||||||
|
// funding multisig output script is known. We'll create an
|
||||||
|
// intent that will supervise the multi-step process.
|
||||||
|
case in.FundingShim.GetPsbtShim() != nil:
|
||||||
|
psbtShim := in.FundingShim.GetPsbtShim()
|
||||||
|
|
||||||
|
// Instruct the wallet to use the new
|
||||||
|
// chanfunding.PsbtAssembler to construct the funding
|
||||||
|
// transaction.
|
||||||
|
copy(req.pendingChanID[:], psbtShim.PendingChanId)
|
||||||
|
req.chanFunder, err = newPsbtAssembler(
|
||||||
|
in, minConfs, psbtShim,
|
||||||
|
&r.server.cc.wallet.Cfg.NetParams,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -6236,6 +6303,7 @@ func (r *rpcServer) BakeMacaroon(ctx context.Context,
|
|||||||
func (r *rpcServer) FundingStateStep(ctx context.Context,
|
func (r *rpcServer) FundingStateStep(ctx context.Context,
|
||||||
in *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) {
|
in *lnrpc.FundingTransitionMsg) (*lnrpc.FundingStateStepResp, error) {
|
||||||
|
|
||||||
|
var pendingChanID [32]byte
|
||||||
switch {
|
switch {
|
||||||
|
|
||||||
// If this is a message to register a new shim that is an external
|
// If this is a message to register a new shim that is an external
|
||||||
@@ -6269,7 +6337,6 @@ func (r *rpcServer) FundingStateStep(ctx context.Context,
|
|||||||
// Once we receive an incoming funding request that uses this
|
// Once we receive an incoming funding request that uses this
|
||||||
// pending channel ID, then this shim will be dispatched in
|
// pending channel ID, then this shim will be dispatched in
|
||||||
// place of our regular funding workflow.
|
// place of our regular funding workflow.
|
||||||
var pendingChanID [32]byte
|
|
||||||
copy(pendingChanID[:], rpcShimIntent.PendingChanId)
|
copy(pendingChanID[:], rpcShimIntent.PendingChanId)
|
||||||
err = r.server.cc.wallet.RegisterFundingIntent(
|
err = r.server.cc.wallet.RegisterFundingIntent(
|
||||||
pendingChanID, shimIntent,
|
pendingChanID, shimIntent,
|
||||||
@@ -6278,18 +6345,71 @@ func (r *rpcServer) FundingStateStep(ctx context.Context,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// There is no need to register a PSBT shim before opening the channel,
|
||||||
|
// even though our RPC message structure allows for it. Inform the user
|
||||||
|
// by returning a proper error instead of just doing nothing.
|
||||||
|
case in.GetShimRegister() != nil &&
|
||||||
|
in.GetShimRegister().GetPsbtShim() != nil:
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("PSBT shim must only be sent when " +
|
||||||
|
"opening a channel")
|
||||||
|
|
||||||
// If this is a transition to cancel an existing shim, then we'll pass
|
// If this is a transition to cancel an existing shim, then we'll pass
|
||||||
// this message along to the wallet.
|
// this message along to the wallet, informing it that the intent no
|
||||||
|
// longer needs to be considered and should be cleaned up.
|
||||||
case in.GetShimCancel() != nil:
|
case in.GetShimCancel() != nil:
|
||||||
pid := in.GetShimCancel().PendingChanId
|
rpcsLog.Debugf("Canceling funding shim for pending_id=%x",
|
||||||
|
in.GetShimCancel().PendingChanId)
|
||||||
var pendingChanID [32]byte
|
|
||||||
copy(pendingChanID[:], pid)
|
|
||||||
|
|
||||||
|
copy(pendingChanID[:], in.GetShimCancel().PendingChanId)
|
||||||
err := r.server.cc.wallet.CancelFundingIntent(pendingChanID)
|
err := r.server.cc.wallet.CancelFundingIntent(pendingChanID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this is a transition to verify the PSBT for an existing shim,
|
||||||
|
// we'll do so and then store the verified PSBT for later so we can
|
||||||
|
// compare it to the final, signed one.
|
||||||
|
case in.GetPsbtVerify() != nil:
|
||||||
|
rpcsLog.Debugf("Verifying PSBT for pending_id=%x",
|
||||||
|
in.GetPsbtVerify().PendingChanId)
|
||||||
|
|
||||||
|
copy(pendingChanID[:], in.GetPsbtVerify().PendingChanId)
|
||||||
|
packet, err := psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader(in.GetPsbtVerify().FundedPsbt), false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing psbt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.server.cc.wallet.PsbtFundingVerify(
|
||||||
|
pendingChanID, packet,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a transition to finalize the PSBT funding flow, we compare
|
||||||
|
// the final PSBT to the previously verified one and if nothing
|
||||||
|
// unexpected was changed, continue the channel opening process.
|
||||||
|
case in.GetPsbtFinalize() != nil:
|
||||||
|
rpcsLog.Debugf("Finalizing PSBT for pending_id=%x",
|
||||||
|
in.GetPsbtFinalize().PendingChanId)
|
||||||
|
|
||||||
|
copy(pendingChanID[:], in.GetPsbtFinalize().PendingChanId)
|
||||||
|
packet, err := psbt.NewFromRawBytes(
|
||||||
|
bytes.NewReader(in.GetPsbtFinalize().SignedPsbt), false,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error parsing psbt: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.server.cc.wallet.PsbtFundingFinalize(
|
||||||
|
pendingChanID, packet,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(roasbeef): extend PendingChannels to also show shims
|
// TODO(roasbeef): extend PendingChannels to also show shims
|
||||||
|
|||||||
Reference in new issue
Block a user