diff --git a/assets/client.go b/assets/client.go index 717a6baf..6b8b514f 100644 --- a/assets/client.go +++ b/assets/client.go @@ -49,13 +49,21 @@ type TapdConfig struct { // DefaultTapdConfig returns a default configuration to connect to a taproot // assets daemon. func DefaultTapdConfig() *TapdConfig { - defaultTapdDir := btcutil.AppDataDir("tapd", false) + return DefaultTapdConfigForNetwork( + btcutil.AppDataDir("tapd", false), "mainnet", + ) +} +// DefaultTapdConfigForNetwork returns the default tapd configuration rooted in +// defaultTapdDir for the given Bitcoin network. Passing the directory +// explicitly keeps path construction testable without accessing the user's +// real tapd data. +func DefaultTapdConfigForNetwork(defaultTapdDir, network string) *TapdConfig { return &TapdConfig{ Activate: false, Host: "localhost:10029", MacaroonPath: filepath.Join( - defaultTapdDir, "mainnet", "admin.macaroon", + defaultTapdDir, "data", network, "admin.macaroon", ), TLSPath: filepath.Join(defaultTapdDir, "tls.cert"), RFQtimeout: defaultRfqTimeout, diff --git a/assets/client_test.go b/assets/client_test.go index addc63f4..8fa79092 100644 --- a/assets/client_test.go +++ b/assets/client_test.go @@ -1,6 +1,10 @@ package assets import ( + "encoding/pem" + "net/http" + "net/http/httptest" + "os" "path/filepath" "testing" @@ -8,6 +12,7 @@ import ( "github.com/lightninglabs/taproot-assets/taprpc/rfqrpc" "github.com/lightningnetwork/lnd/lnwire" "github.com/stretchr/testify/require" + "gopkg.in/macaroon.v2" ) // TestDefaultTapdConfig tests that the default tapd connection paths match @@ -16,10 +21,62 @@ func TestDefaultTapdConfig(t *testing.T) { defaultTapdDir := btcutil.AppDataDir("tapd", false) config := DefaultTapdConfig() + require.Equal(t, filepath.Join( + defaultTapdDir, "data", "mainnet", "admin.macaroon", + ), config.MacaroonPath) require.Equal( - t, filepath.Join(defaultTapdDir, "mainnet", "admin.macaroon"), - config.MacaroonPath, + t, filepath.Join(defaultTapdDir, "tls.cert"), config.TLSPath, ) +} + +// TestTapdConfigClientConn tests that the default tapd file layout can be used +// to construct a client connection. +func TestTapdConfigClientConn(t *testing.T) { + // Use an isolated tapd root so the test never reads from or writes to a + // user's real tapd data directory. + defaultTapdDir := t.TempDir() + network := "regtest" + macaroonPath := filepath.Join( + defaultTapdDir, "data", network, "admin.macaroon", + ) + require.NoError(t, os.MkdirAll(filepath.Dir(macaroonPath), 0o700)) + + // NewTapdClient parses the configured TLS certificate before creating + // its gRPC client. An httptest server provides a valid certificate + // without requiring a running tapd instance. + tlsServer := httptest.NewTLSServer(http.NotFoundHandler()) + t.Cleanup(tlsServer.Close) + cert := tlsServer.Certificate() + certBytes := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", Bytes: cert.Raw, + }) + require.NoError(t, os.WriteFile( + filepath.Join(defaultTapdDir, "tls.cert"), certBytes, 0o600, + )) + + // Store a valid serialized macaroon at tapd's production path. This + // ensures connection setup tests the path itself rather than failing on + // malformed credentials. + mac, err := macaroon.New( + []byte("root-key"), []byte("id"), "tapd", + macaroon.LatestVersion, + ) + require.NoError(t, err) + macBytes, err := mac.MarshalBinary() + require.NoError(t, err) + require.NoError(t, os.WriteFile(macaroonPath, macBytes, 0o600)) + + // grpc.NewClient connects lazily, so constructing the tapd client verifies + // that both credentials can be loaded and parsed without needing a live + // tapd server. + config := DefaultTapdConfigForNetwork(defaultTapdDir, network) + client, err := NewTapdClient(config) + require.NoError(t, err) + t.Cleanup(func() { + require.NoError(t, client.cc.Close()) + }) + + require.Equal(t, macaroonPath, config.MacaroonPath) require.Equal( t, filepath.Join(defaultTapdDir, "tls.cert"), config.TLSPath, ) diff --git a/loopd/config.go b/loopd/config.go index 0e0e065e..bacfc45a 100644 --- a/loopd/config.go +++ b/loopd/config.go @@ -335,6 +335,14 @@ func Validate(cfg *Config) error { ) } + // If the user doesn't specify Tapd.MacaroonPath, reassemble it with + // the configured Bitcoin network. + if cfg.Tapd.MacaroonPath == assets.DefaultTapdConfig().MacaroonPath { + cfg.Tapd.MacaroonPath = assets.DefaultTapdConfigForNetwork( + btcutil.AppDataDir("tapd", false), cfg.Network, + ).MacaroonPath + } + // We'll also update the database file location as well, if it wasn't // set. if cfg.Sqlite.DatabaseFileName == defaultSqliteDatabasePath { diff --git a/loopd/config_test.go b/loopd/config_test.go new file mode 100644 index 00000000..87eda5fa --- /dev/null +++ b/loopd/config_test.go @@ -0,0 +1,51 @@ +package loopd + +import ( + "path/filepath" + "testing" + + "github.com/btcsuite/btcd/btcutil" + "github.com/lightninglabs/loop/assets" + "github.com/stretchr/testify/require" +) + +// TestValidateTapdMacaroonPath tests that validation updates the default tapd +// macaroon path for the configured network without changing an explicit path. +func TestValidateTapdMacaroonPath(t *testing.T) { + customPath := filepath.Join(t.TempDir(), "custom.macaroon") + defaultConfig := assets.DefaultTapdConfig() + regtestConfig := assets.DefaultTapdConfigForNetwork( + btcutil.AppDataDir("tapd", false), "regtest", + ) + + tests := []struct { + name string + macaroonPath string + expectedPath string + }{ + { + name: "default path", + macaroonPath: defaultConfig.MacaroonPath, + expectedPath: regtestConfig.MacaroonPath, + }, + { + name: "explicit path", + macaroonPath: customPath, + expectedPath: customPath, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + cfg := DefaultConfig() + cfg.Network = "regtest" + cfg.LoopDir = t.TempDir() + cfg.Tapd.MacaroonPath = test.macaroonPath + + require.NoError(t, Validate(&cfg)) + require.Equal( + t, test.expectedPath, cfg.Tapd.MacaroonPath, + ) + }) + } +}