Skip to content

Commit

Permalink
wip-funding-controller
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed May 17, 2024
1 parent 80b004a commit f616b28
Show file tree
Hide file tree
Showing 18 changed files with 2,957 additions and 207 deletions.
30 changes: 30 additions & 0 deletions chain_bridge.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package taprootassets

import (
"bytes"
"context"
"fmt"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/taproot-assets/rfq"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightningnetwork/lnd/chainntnfs"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc/verrpc"
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
)

// LndRpcChainBridge is an implementation of the tapgarden.ChainBridge
Expand Down Expand Up @@ -236,6 +241,31 @@ func (l *LndMsgTransportClient) SendCustomMessage(ctx context.Context,
return l.lnd.Client.SendCustomMessage(ctx, msg)
}

// SendMessage sends a message to a remote peer.
func (l *LndMsgTransportClient) SendMessage(ctx context.Context,
peer btcec.PublicKey, msg lnwire.Message) error {

var buf bytes.Buffer
if err := msg.Encode(&buf, 0); err != nil {
return fmt.Errorf("unable to encode message: %w", err)
}

return l.SendCustomMessage(ctx, lndclient.CustomMessage{
Peer: route.NewVertex(&peer),
MsgType: uint32(msg.MsgType()),
Data: buf.Bytes(),
})
}

// TODO(guggero): Actually implement. But this is robably the wrong place for
// this method anyway...
func (l *LndMsgTransportClient) ReportError(pid funding.PendingChanID,
err error) {

srvrLog.Errorf("Error in funding flow for pending chan ID %x: %v",
pid[:], err)
}

// Ensure LndMsgTransportClient implements the rfq.PeerMessenger interface.
var _ rfq.PeerMessenger = (*LndMsgTransportClient)(nil)

Expand Down
2 changes: 2 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ type Config struct {

AuxLeafSigner *tapchannel.AuxLeafSigner

AuxFundingController *tapchannel.FundingController

// UniversePublicAccess is flag which, If true, and the Universe server
// is on a public interface, valid proof from remote parties will be
// accepted, and proofs will be queryable by remote parties.
Expand Down
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ require (
github.com/lib/pq v1.10.9
github.com/lightninglabs/aperture v0.1.21-beta.0.20230705004936-87bb996a4030
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2
github.com/lightninglabs/lndclient v1.0.1-0.20240301093332-e39221e5a641
github.com/lightninglabs/lndclient v1.0.1-0.20240517141753-972bc2d77cd8
github.com/lightninglabs/neutrino/cache v1.1.2
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240501135111-81970eac6a5a
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517141520-5a834c15ba73
github.com/lightningnetwork/lnd/cert v1.2.2
github.com/lightningnetwork/lnd/clock v1.1.1
github.com/lightningnetwork/lnd/fn v1.0.5
Expand Down Expand Up @@ -78,7 +78,7 @@ require (
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
github.com/decred/dcrd/lru v1.0.0 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v24.0.7+incompatible // indirect
github.com/docker/docker v24.0.9+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.5.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
Expand Down Expand Up @@ -209,4 +209,4 @@ require (
replace google.golang.org/protobuf => github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display

// Remove when https://github.com/lightningnetwork/lnd/pull/8641 is merged.
replace github.com/lightningnetwork/lnd => github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517123451-fedcac2ea8e7
replace github.com/lightningnetwork/lnd => github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517132030-f9b366e35823
12 changes: 6 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,8 @@ github.com/docker/cli v20.10.17+incompatible h1:eO2KS7ZFeov5UJeaDmIs1NFEDRf32Paq
github.com/docker/cli v20.10.17+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v24.0.7+incompatible h1:Wo6l37AuwP3JaMnZa226lzVXGA3F9Ig1seQen0cKYlM=
github.com/docker/docker v24.0.7+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0=
github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
Expand Down Expand Up @@ -477,8 +477,8 @@ github.com/lightninglabs/lightning-node-connect v0.2.5-alpha h1:ZRVChwczFXK0CEbx
github.com/lightninglabs/lightning-node-connect v0.2.5-alpha/go.mod h1:A9Pof9fETkH+F67BnOmrBDThPKstqp73wlImWOZvTXQ=
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2 h1:Er1miPZD2XZwcfE4xoS5AILqP1mj7kqnhbBSxW9BDxY=
github.com/lightninglabs/lightning-node-connect/hashmailrpc v1.0.2/go.mod h1:antQGRDRJiuyQF6l+k6NECCSImgCpwaZapATth2Chv4=
github.com/lightninglabs/lndclient v1.0.1-0.20240301093332-e39221e5a641 h1:gOzlAcE1EL8prHCISjDpFD1tvb9HDxtLEdTVD+ebuc0=
github.com/lightninglabs/lndclient v1.0.1-0.20240301093332-e39221e5a641/go.mod h1:FuD4SXEL3t4J5bu04O+N+k4UcPjsUvGAaT9csWfovbw=
github.com/lightninglabs/lndclient v1.0.1-0.20240517141753-972bc2d77cd8 h1:6lG06uiewbv6CjQN3CnfWl4W995yq3Uzh1LPeMFKclU=
github.com/lightninglabs/lndclient v1.0.1-0.20240517141753-972bc2d77cd8/go.mod h1:19rtwcCfe1c4att8UozKgdI4SJeKne9dp0dyFVR1HvU=
github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0=
github.com/lightninglabs/neutrino v0.16.0/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk=
github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
Expand All @@ -487,8 +487,8 @@ github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wl
github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f h1:Pua7+5TcFEJXIIZ1I2YAUapmbcttmLj4TTi786bIi3s=
github.com/lightningnetwork/lightning-onion v1.2.1-0.20230823005744-06182b1d7d2f/go.mod h1:c0kvRShutpj3l6B9WtTsNTBUtjSmjZXbJd9ZBRQOSKI=
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517123451-fedcac2ea8e7 h1:RBzE+rhBybdTyFGPHNCUACiU4rv7lG7aaAYHZAfjhy4=
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517123451-fedcac2ea8e7/go.mod h1:8AY9Zlz00nSWNKhjb3D3GNL3VP7LyJUjyture7toz2o=
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517132030-f9b366e35823 h1:Vog61NDef990rKutZq08HQ5wn6pIgu9bjZgCEKuIiG4=
github.com/lightningnetwork/lnd v0.17.0-beta.rc6.0.20240517132030-f9b366e35823/go.mod h1:8AY9Zlz00nSWNKhjb3D3GNL3VP7LyJUjyture7toz2o=
github.com/lightningnetwork/lnd/cert v1.2.2 h1:71YK6hogeJtxSxw2teq3eGeuy4rHGKcFf0d0Uy4qBjI=
github.com/lightningnetwork/lnd/cert v1.2.2/go.mod h1:jQmFn/Ez4zhDgq2hnYSw8r35bqGVxViXhX6Cd7HXM6U=
github.com/lightningnetwork/lnd/clock v1.1.1 h1:OfR3/zcJd2RhH0RU+zX/77c0ZiOnIMsDIBjgjWdZgA0=
Expand Down
139 changes: 139 additions & 0 deletions lnd_psbt_channel_funder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package taprootassets

import (
"bytes"
"context"
"fmt"

"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/lightninglabs/lndclient"
"github.com/lightninglabs/taproot-assets/tapchannel"
"github.com/lightningnetwork/lnd/funding"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/routing/route"
)

// LndPbstChannelFunder is an implementation of the tapchannel.ChannelFunder
// interface that uses lnd to carry out the PSBT funding process.
type LndPbstChannelFunder struct {
lnd *lndclient.LndServices
}

// NewLndPbstChannelFunder creates a new LndPbstChannelFunder instance.
func NewLndPbstChannelFunder(lnd *lndclient.LndServices) *LndPbstChannelFunder {
return &LndPbstChannelFunder{
lnd: lnd,
}
}

// assetChanIntent is a concrete implementation of the
// tapchannel.AssetChanIntent.
type assetChanIntent struct {
psbtTemplate *psbt.Packet

lnd *lndclient.LndServices

tempPID funding.PendingChanID
}

// FundingPsbt is the original PsbtTemplate, plus the P2TR funding output
// that'll create the channel.
func (a *assetChanIntent) FundingPsbt() (*psbt.Packet, error) {
return a.psbtTemplate, nil
}

// BindPsbt accepts a new *unsigned* PSBT with any additional inputs or outputs
// (for change) added. This PSBT is still unsigned. This step performs final
// verification to ensure the PSBT is crafted in a manner that'll properly open
// the channel once broadcaster.
func (a *assetChanIntent) BindPsbt(ctx context.Context, finalPSBT *psbt.Packet) error {
var psbtBuf bytes.Buffer
if err := finalPSBT.Serialize(&psbtBuf); err != nil {
return fmt.Errorf("unable to serialize base PSBT: %w", err)
}

_, err := a.lnd.Client.FundingStateStep(
ctx, &lnrpc.FundingTransitionMsg{
Trigger: &lnrpc.FundingTransitionMsg_PsbtVerify{
PsbtVerify: &lnrpc.FundingPsbtVerify{
PendingChanId: a.tempPID[:],
FundedPsbt: psbtBuf.Bytes(),
SkipFinalize: true,
},
},
},
)
return err
}

// OpenChannel attempts to open a new asset holding private channel using the
// backing lnd node. The PSBT flow is by default. An AssetChanIntent is
// returned that includes the updated PSBT template that includes the funding
// output. Once all other inputs+outputs have been added, then BindPsbt should
// be called to progress the funding process. Afterwards, the funding
// transaction should be signed+broadcast.
//
// NOTE: This is part of the tapchannel.ChannelFunder interface.
func (l *LndPbstChannelFunder) OpenChannel(ctx context.Context,
req tapchannel.OpenChanReq) (tapchannel.AssetChanIntent, error) {

var psbtBuf bytes.Buffer
if req.PsbtTemplate != nil {
err := req.PsbtTemplate.Serialize(&psbtBuf)
if err != nil {
return nil, fmt.Errorf("unable to serialize base "+
"PSBT: %w", err)
}
}

// We'll map our high level params into a request for a: private,
// taproot channel, that uses the PSBT funding flow.
taprootCommitType := lnrpc.CommitmentType_SIMPLE_TAPROOT
openChanStream, errChan, err := l.lnd.Client.OpenChannelStream(
ctx, route.NewVertex(&req.PeerPub), req.ChanAmt, 0, true,
lndclient.WithCommitmentType(&taprootCommitType),
lndclient.WithFundingShim(&lnrpc.FundingShim{
Shim: &lnrpc.FundingShim_PsbtShim{
PsbtShim: &lnrpc.PsbtShim{
PendingChanId: req.TempPID[:],
NoPublish: true,
BasePsbt: psbtBuf.Bytes(),
},
},
}),
)
if err != nil {
return nil, fmt.Errorf("unable to open channel with "+
"lnd: %w", err)
}

// With our request extended, we'll now wait for the initial response
// sent after the responder sends AcceptChannel.
select {
case resp := <-openChanStream:
// Assert that we have a PSBT response from the node.
if resp.PsbtFund == nil {
return nil, fmt.Errorf("expected PSBT funding response")
}

fundingPSBT, err := psbt.NewFromRawBytes(
bytes.NewReader(resp.PsbtFund.Psbt), false,
)
if err != nil {
return nil, fmt.Errorf("unable to parse PSBT: %w", err)
}

return &assetChanIntent{
psbtTemplate: fundingPSBT,
lnd: l.lnd,
tempPID: req.TempPID,
}, nil

case err := <-errChan:
return nil, err
}
}

// A compile-time check to ensure that LndPbstChannelFunder fully implements
// the tapchannel.PsbtChannelFunder interface.
var _ tapchannel.PsbtChannelFunder = (*LndPbstChannelFunder)(nil)
7 changes: 6 additions & 1 deletion perms/perms.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ var (
Entity: "assets",
Action: "read",
}},
"/taprpc.TaprootAssets/FundChannel": {{
Entity: "assets",
Action: "write",
}},
"/assetwalletrpc.AssetWallet/FundVirtualPsbt": {{
Entity: "assets",
Action: "write",
Expand Down Expand Up @@ -259,7 +263,8 @@ var (
"/tapdevrpc.TapDev/SubscribeReceiveAssetEventNtfns": {{
Entity: "assets",
Action: "write",
}}}
}},
}

// defaultMacaroonWhitelist defines a default set of RPC endpoints that
// don't require macaroons authentication.
Expand Down
32 changes: 32 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/lightninglabs/taproot-assets/rfq"
"github.com/lightninglabs/taproot-assets/rfqmsg"
"github.com/lightninglabs/taproot-assets/rpcperms"
"github.com/lightninglabs/taproot-assets/tapchannel"
"github.com/lightninglabs/taproot-assets/tapfreighter"
"github.com/lightninglabs/taproot-assets/tapgarden"
"github.com/lightninglabs/taproot-assets/tappsbt"
Expand Down Expand Up @@ -6158,6 +6159,37 @@ func (r *rpcServer) SubscribeRfqEventNtfns(
)
}

func (r *rpcServer) FundChannel(ctx context.Context,
req *taprpc.FundChannelRequest) (*taprpc.FundChannelResponse,
error) {

peerPub, err := btcec.ParsePubKey(req.PeerPubkey)
if err != nil {
return nil, fmt.Errorf("error parsing peer pubkey: %w", err)
}

if len(req.AssetId) != sha256.Size {
return nil, fmt.Errorf("asset ID must be 32 bytes")
}

fundReq := tapchannel.FundReq{
PeerPub: *peerPub,
Amt: req.Amount,
FeeRate: chainfee.SatPerVByte(req.FeeRateSatPerVbyte),
}

copy(fundReq.AssetID[:], req.AssetId)

txHash, err := r.cfg.AuxFundingController.FundChannel(ctx, fundReq)
if err != nil {
return nil, fmt.Errorf("error funding channel: %w", err)
}

return &taprpc.FundChannelResponse{
Txid: txHash.String(),
}, nil
}

// serialize is a helper function that serializes a serializable object into a
// byte slice.
func serialize(s interface{ Serialize(io.Writer) error }) ([]byte, error) {
Expand Down
Loading

0 comments on commit f616b28

Please sign in to comment.