From dbac40cfa698cab24df3aed3fc646cb766ba07c5 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Wed, 26 Feb 2025 13:44:16 +0100 Subject: [PATCH] tapchannel: assert proof courier connection To make sure the universe proof courier address configured isn't only formally valid but can also be connected to, we do a quick connection check before requesting or accepting a channel funding action. --- tapchannel/aux_funding_controller.go | 32 ++++++++++-- tapchannel/aux_funding_controller_test.go | 64 ++++++++++++++++++++--- 2 files changed, 86 insertions(+), 10 deletions(-) diff --git a/tapchannel/aux_funding_controller.go b/tapchannel/aux_funding_controller.go index 07ce10ec8..af8315948 100644 --- a/tapchannel/aux_funding_controller.go +++ b/tapchannel/aux_funding_controller.go @@ -53,6 +53,11 @@ const ( // level ACK from the remote party before timing out. ackTimeout = time.Second * 30 + // proofCourierCheckTimeout is the amount of time we'll wait before we + // time out an attempt to connect to a proof courier when checking the + // configured address. + proofCourierCheckTimeout = time.Second * 5 + // maxNumAssetIDs is the maximum number of fungible asset pieces (asset // IDs) that can be committed to a single channel. The number needs to // be limited to prevent the number of required HTLC signatures to be @@ -1359,7 +1364,7 @@ func (f *FundingController) processFundingMsg(ctx context.Context, // can't deal with the OP_TRUE funding output script key, as that's the // same for asset channels out there. So the single mailbox would always // be occupied. - if err := f.validateLocalProofCourier(); err != nil { + if err := f.validateLocalProofCourier(ctx); err != nil { return tempPID, fmt.Errorf("unable to accept channel funding "+ "request, local proof courier is unsupported: %w", err) } @@ -1529,7 +1534,7 @@ func (f *FundingController) processFundingReq(fundingFlows fundingFlowIndex, // with the OP_TRUE funding output script key, as that's the same for // asset channels out there. So the single mailbox would always be // occupied. - if err := f.validateLocalProofCourier(); err != nil { + if err := f.validateLocalProofCourier(fundReq.ctx); err != nil { return fmt.Errorf("unable to fund channel, local proof "+ "courier is unsupported: %w", err) } @@ -2050,7 +2055,9 @@ func (f *FundingController) validateWitness(outAsset asset.Asset, // validateLocalProofCourier checks if the local proof courier is supported by // the funding controller. This is necessary to ensure that we can accept // incoming asset channel funding requests. -func (f *FundingController) validateLocalProofCourier() error { +func (f *FundingController) validateLocalProofCourier( + ctx context.Context) error { + courierURL := f.cfg.DefaultCourierAddr flagHelp := "please set a universe based (universerpc://) proof " + @@ -2070,6 +2077,25 @@ func (f *FundingController) validateLocalProofCourier() error { courierURL.Scheme, flagHelp) } + // We now also make a quick test connection. + ctxt, cancel := context.WithTimeout(ctx, proofCourierCheckTimeout) + defer cancel() + courier, err := proof.NewUniverseRpcCourier( + ctxt, &proof.UniverseRpcCourierCfg{}, nil, nil, courierURL, + false, + ) + if err != nil { + return fmt.Errorf("unable to test connection proof courier "+ + "'%v': %v", courierURL.String(), err) + } + + err = courier.Close() + if err != nil { + // We only log any disconnect errors, as they're not critical. + log.Warnf("Unable to disconnect from proof courier '%v': %v", + courierURL.String(), err) + } + return nil } diff --git a/tapchannel/aux_funding_controller_test.go b/tapchannel/aux_funding_controller_test.go index 7b57a5195..d99c9d744 100644 --- a/tapchannel/aux_funding_controller_test.go +++ b/tapchannel/aux_funding_controller_test.go @@ -1,16 +1,26 @@ package tapchannel import ( + "context" "fmt" "net/url" "testing" + "github.com/lightninglabs/taproot-assets/internal/test" "github.com/lightninglabs/taproot-assets/proof" + "github.com/lightninglabs/taproot-assets/taprpc/universerpc" + "github.com/lightningnetwork/lnd/lntest/port" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" ) -func dummyURL(t *testing.T, protocol string) *url.URL { - urlString := fmt.Sprintf("%s://localhost:1234", protocol) +type mockUniverseServer struct { + universerpc.UnimplementedUniverseServer +} + +func dummyURL(t *testing.T, protocol, addr string) *url.URL { + urlString := fmt.Sprintf("%s://%s", protocol, addr) proofCourierAddr, err := proof.ParseCourierAddress(urlString) require.NoError(t, err) @@ -18,18 +28,49 @@ func dummyURL(t *testing.T, protocol string) *url.URL { } func TestValidateLocalProofCourier(t *testing.T) { + serverOpts := []grpc.ServerOption{ + grpc.Creds(insecure.NewCredentials()), + } + grpcServer := grpc.NewServer(serverOpts...) + + server := mockUniverseServer{} + universerpc.RegisterUniverseServer(grpcServer, &server) + + // We also grab a port that is free to listen on for our negative test. + // Since we know the port is free, and we don't listen on it, we expect + // the connection to fail. + noConnectPort := port.NextAvailablePort() + noConnectAddr := fmt.Sprintf(test.ListenAddrTemplate, noConnectPort) + + mockServerAddr, cleanup, err := test.StartMockGRPCServer( + t, grpcServer, true, + ) + require.NoError(t, err) + t.Cleanup(cleanup) + tests := []struct { name string courierAddr *url.URL expectErr string }{ { - name: "valid universe rpc courier", - courierAddr: dummyURL(t, proof.UniverseRpcCourierType), + name: "valid universe rpc courier", + courierAddr: dummyURL( + t, proof.UniverseRpcCourierType, mockServerAddr, + ), }, { - name: "invalid courier type", - courierAddr: dummyURL(t, proof.HashmailCourierType), + name: "valid universe rpc courier, but can't connect", + courierAddr: dummyURL( + t, proof.UniverseRpcCourierType, noConnectAddr, + ), + expectErr: "unable to connect to courier service", + }, + { + name: "invalid courier type", + courierAddr: dummyURL( + t, proof.HashmailCourierType, mockServerAddr, + ), expectErr: "unsupported proof courier type " + "'hashmail'", }, @@ -52,7 +93,16 @@ func TestValidateLocalProofCourier(t *testing.T) { }, } - err := fc.validateLocalProofCourier() + // We use a short timeout here, since we don't want to + // wait for the full default timeout of the funding + // controller + ctxb := context.Background() + ctxb, cancel := context.WithTimeout( + ctxb, test.StartupWaitTime*2, + ) + defer cancel() + + err := fc.validateLocalProofCourier(ctxb) if tt.expectErr != "" { require.ErrorContains(t, err, tt.expectErr)