diff --git a/feature_bit_verifier.go b/feature_bit_verifier.go new file mode 100644 index 000000000..57e18641b --- /dev/null +++ b/feature_bit_verifier.go @@ -0,0 +1,54 @@ +package taprootassets + +import ( + "context" + + "github.com/btcsuite/btcd/btcec/v2" + "github.com/lightninglabs/lndclient" + "github.com/lightninglabs/taproot-assets/tapchannel" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/lightningnetwork/lnd/routing/route" +) + +// LndFeatureBitVerifier is a struct that verifies that the feature bits of a +// target connected peer, using our registered lnd node. +type LndFeatureBitVerifier struct { + lnd *lndclient.LndServices +} + +// NewLndFeatureBitVerifier creates a new LndFeatureBitVerifier instance. +func NewLndFeatureBitVerifier( + lnd *lndclient.LndServices) *LndFeatureBitVerifier { + + return &LndFeatureBitVerifier{ + lnd: lnd, + } +} + +// HasFeature returns true if the peer has the given feature bit set. If the +// peer can't be found, then ErrNoPeer is returned. +func (l *LndFeatureBitVerifier) HasFeature(ctx context.Context, + peerPub btcec.PublicKey, bit lnwire.FeatureBit) (bool, error) { + + peerBytes := route.NewVertex(&peerPub) + + peers, err := l.lnd.Client.ListPeers(ctx) + if err != nil { + return false, err + } + + for _, peer := range peers { + if peer.Pubkey != peerBytes { + continue + } + + return peer.Features.HasFeature(bit), nil + } + + // If we get to this point, we weren't able to find the peer. + return false, tapchannel.ErrNoPeer +} + +// A compile-time check to ensure that LndFeatureBitVerifier implements the +// FeatureBitVerifier interface. +var _ tapchannel.FeatureBitVerifer = (*LndFeatureBitVerifier)(nil) diff --git a/go.mod b/go.mod index 599d3d5ae..9687e56ac 100644 --- a/go.mod +++ b/go.mod @@ -28,7 +28,7 @@ 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.20240607082608-4ce52a1a3f27 + github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf github.com/lightninglabs/neutrino/cache v1.1.2 github.com/lightningnetwork/lnd v0.18.0-beta.rc4.0.20240723043204-f09d4042aee4 github.com/lightningnetwork/lnd/cert v1.2.2 diff --git a/go.sum b/go.sum index 36c884a8b..f4c25a08d 100644 --- a/go.sum +++ b/go.sum @@ -480,8 +480,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.20240607082608-4ce52a1a3f27 h1:vm8a13EzH2Qe6j4eZx+tHPeEVoNhJ7coihFPX6K2kco= -github.com/lightninglabs/lndclient v1.0.1-0.20240607082608-4ce52a1a3f27/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= +github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf h1:VcTK/juPtAqwEBckCcSHCsVRSbHGbWtDZgnXL5JOLkg= +github.com/lightninglabs/lndclient v1.0.1-0.20240723001046-925d3c8297bf/go.mod h1:bxd2a15cIaW8KKcmOf9nNDI/GTxxj0upEYs1EIkttqw= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s= github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= diff --git a/tapcfg/server.go b/tapcfg/server.go index 302a59825..5adc86482 100644 --- a/tapcfg/server.go +++ b/tapcfg/server.go @@ -102,6 +102,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, msgTransportClient := tap.NewLndMsgTransportClient(lndServices) lndRouterClient := tap.NewLndRouterClient(lndServices) lndInvoicesClient := tap.NewLndInvoicesClient(lndServices) + lndFeatureBitsVerifier := tap.NewLndFeatureBitVerifier(lndServices) uniDB := tapdb.NewTransactionExecutor( db, func(tx *sql.Tx) tapdb.BaseUniverseStore { @@ -434,6 +435,7 @@ func genServerConfig(cfg *Config, cfgLogger btclog.Logger, TxSender: chainPorter, DefaultCourierAddr: proofCourierAddr, AssetSyncer: addrBook, + FeatureBits: lndFeatureBitsVerifier, }, ) auxTrafficShaper := tapchannel.NewAuxTrafficShaper( diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index 5be7b63d8..6de256245 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -70,6 +70,18 @@ type PeerMessenger interface { msg lnwire.Message) error } +// ErrNoPeer is returned when a peer can't be found. +var ErrNoPeer = errors.New("peer not found") + +// FeatureBitVerifer is an interface that allows us to verify that a peer has a +// given feature bit set. +type FeatureBitVerifer interface { + // HasFeature returns true if the peer has the given feature bit set. + // If the peer can't be found, then ErrNoPeer is returned. + HasFeature(ctx context.Context, peerPub btcec.PublicKey, + bit lnwire.FeatureBit) (bool, error) +} + // OpenChanReq is a request to open a new asset channel with a remote peer. type OpenChanReq struct { // ChanAmt is the amount of BTC to put into the channel. Some BTC is @@ -200,6 +212,10 @@ type FundingControllerCfg struct { // AssetSyncer is used to ensure that we've already verified the asset // genesis for any assets used within channels. AssetSyncer AssetSyncer + + // FeatureBits is used to verify that the peer has the required feature + // to fund asset channels. + FeatureBits FeatureBitVerifer } // bindFundingReq is a request to bind a pending channel ID to a complete aux @@ -1322,6 +1338,21 @@ func (f *FundingController) processFundingMsg(ctx context.Context, func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, fundReq *FundReq) error { + // Before we even attempt funding, let's make sure that the remote peer + // actually supports the feature bit. + supportsAssetChans, err := f.cfg.FeatureBits.HasFeature( + fundReq.ctx, fundReq.PeerPub, + lnwire.SimpleTaprootOverlayChansOptional, + ) + if err != nil { + return fmt.Errorf("unable to query peer feature bits: %w", err) + } + + if !supportsAssetChans { + return fmt.Errorf("peer %x does not support asset channels", + fundReq.PeerPub.SerializeCompressed()) + } + // To start, we'll make a new pending asset funding desc. This'll be // our scratch pad during the asset funding process. tempPID, err := newPendingChanID()