What changed, and why it matters
This commit only adds an integration test that checks an existing safety feature: LND refuses to start if the Bitcoin network setting doesn't match the one already recorded in a Postgres database. It does not change production code or fix a new vulnerability. The test helps prevent accidental data corruption from reusing the same database across different Bitcoin networks, but it is not itself a security patch.
No security action required. Treat as normal test-coverage addition. If reviewing for release notes, note it improves test coverage for an existing data-integrity safeguard.
Security signals we found
New integration test for existing network-mismatch guard
No production code changes
No bug fix or vulnerability patch present in diff
Evidence from the diff
The commit adds a new integration test, testPostgresNetworkSeparation, plus a harness helper IsPostgresBackend(). The test verifies that chainparams.ValidateNetwork returns ErrNetworkMismatch when LND is configured for simnet but the Postgres chain_params table contains regtest. It also confirms the process exits early on mismatch. No production logic is modified; the underlying validation behavior already exists and is separately unit-tested for SQLite.
Changed components
itest/lnd_postgres_network_separation_test.goitest/list_on_test.golntest/harness.goInspect captured patch +99 / −0
diff --git a/itest/list_on_test.go b/itest/list_on_test.go
index 3e6bc9f..16517bc 100644
--- a/itest/list_on_test.go
+++ b/itest/list_on_test.go
@@ -791,6 +791,10 @@ var allTestCases = []*lntest.TestCase{
Name: "estimate on chain fee auto selected inputs",
TestFunc: testEstimateOnChainFeeAutoSelectedInputs,
},
+ {
+ Name: "postgres network separation",
+ TestFunc: testPostgresNetworkSeparation,
+ },
}
// appendPrefixed is used to add a prefix to each test name in the subtests
diff --git a/itest/lnd_postgres_network_separation_test.go b/itest/lnd_postgres_network_separation_test.go
new file mode 100644
index 0000000..d950d8c
--- /dev/null
+++ b/itest/lnd_postgres_network_separation_test.go
@@ -0,0 +1,89 @@
+package itest
+
+import (
+ "github.com/btcsuite/btcd/chaincfg"
+ "github.com/lightningnetwork/lnd/chainparams"
+ "github.com/lightningnetwork/lnd/lntest"
+ "github.com/lightningnetwork/lnd/sqldb"
+ "github.com/stretchr/testify/require"
+)
+
+// testPostgresNetworkSeparation verifies that lnd refuses to start when the
+// active Bitcoin network does not match the network stored in the postgres
+// chain_params table. This prevents silent data corruption that would occur if
+// a user accidentally reused the same postgres DSN across different networks.
+//
+// Note: the equivalent SQLite scenario (reusing the same .db file across
+// networks) is not covered here because the itest harness does not provide a
+// direct path to inject an existing SQLite file into a new node. The feature
+// is exercised for SQLite at the unit-test level in chainparams/store_test.go.
+//
+// The test flow is:
+// 1. Start lnd on regtest with native SQL enabled → first startup writes
+// "regtest" into the chain_params table.
+// 2. Restart with the same postgres DSN and regtest → must succeed, proving
+// ValidateNetwork passes when the active network matches the stored one.
+// 3. Stop the node.
+// 4. Restart lnd with the same postgres DSN but switch to simnet → lnd must
+// detect the network mismatch and exit with an error.
+func testPostgresNetworkSeparation(ht *lntest.HarnessTest) {
+ // This test is only relevant for the postgres backend with native SQL.
+ // The SQLite equivalent is covered at the unit-test level.
+ if !ht.IsPostgresBackend() {
+ ht.Skip("node not running with postgres backend")
+ }
+
+ // First startup: native SQL applies migrations and persists regtest in
+ // chain_params.
+ alice := ht.NewNodeWithCoins("Alice", []string{"--db.use-native-sql"})
+
+ // Second startup: same DSN and network — ValidateNetwork must succeed;
+ // proves the matching path against a real DB, not only unit tests.
+ ht.RestartNode(alice)
+
+ require.NoError(ht, alice.Stop())
+
+ // Direct store check: simnet vs stored regtest must be
+ // ErrNetworkMismatch, independent of whether lnd's process failed for
+ // the right reason.
+ store, err := sqldb.NewPostgresStore(&sqldb.PostgresConfig{
+ Dsn: alice.Cfg.PostgresDsn,
+ Timeout: defaultTimeout,
+ })
+ require.NoError(ht, err)
+ defer store.Close()
+
+ chainParamsStore := chainparams.NewStore(store.GetBaseDB())
+ err = chainParamsStore.ValidateNetwork(
+ ht.Context(), &chaincfg.SimNetParams,
+ )
+ require.ErrorIs(ht, err, chainparams.ErrNetworkMismatch)
+
+ // Process-level check: restart lnd with simnet while the DB still says
+ // regtest — must exit early (ValidateNetwork during startup).
+ //
+ // Now restart alice but override the network to simnet. The DSN still
+ // points at the same postgres database, so lnd should detect the
+ // mismatch and refuse to start.
+ //
+ // ExtraArgs are appended last when building the lnd command line and
+ // therefore take precedence over the generated --bitcoin.regtest flag,
+ // effectively switching the node to simnet.
+ alice.Cfg.NetParams = &chaincfg.SimNetParams
+ alice.SetExtraArgs([]string{
+ "--db.use-native-sql",
+ "--bitcoin.simnet",
+ "--bitcoin.node=neutrino",
+ })
+
+ // StartLndCmd launches the process without waiting for it to become
+ // ready, which is what we want since we expect it to exit early.
+ require.NoError(ht, alice.StartLndCmd(ht.Context()))
+
+ // The process should exit with a non-zero status due to the network
+ // mismatch error returned by ValidateNetwork. We only assert that the
+ // error is non-nil rather than matching the exact OS exit-code string,
+ // because WaitForProcessExit may return a harness-level shutdown error
+ // when the node exits before writing "Shutdown complete" to its log.
+ require.Error(ht, alice.WaitForProcessExit())
+}
diff --git a/lntest/harness.go b/lntest/harness.go
index 66c85cc..c848a7b 100644
--- a/lntest/harness.go
+++ b/lntest/harness.go
@@ -1438,6 +1438,12 @@ func (h *HarnessTest) IsNeutrinoBackend() bool {
return h.manager.chainBackend.Name() == NeutrinoBackendName
}
+// IsPostgresBackend returns true if the test harness is configured to use a
+// Postgres database backend.
+func (h *HarnessTest) IsPostgresBackend() bool {
+ return h.manager.dbBackend == node.BackendPostgres
+}
+
// fundCoins attempts to send amt satoshis from the internal mining node to the
// targeted lightning node. The confirmed boolean indicates whether the
// transaction that pays to the target should confirm. For neutrino backend,
Why this scored 12/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.