Repository: lightningnetwork/lnd
Pull request #6084 (2021-12-13 09:55:48)

channeldb: allow to be used read-only in external tools
Allow read-only access to a DB backend if the `channeldb` package is used as a library in external projects. For example, this is very useful in `chantools`.

bhandras: IUUC this is not exactly read-only mode but just opening without running any migrations right?
guggero: Hmm, yes. I'm using it in `chantools`, where I open the low-level `bbolt` DB in read-only mode. If I don't add this flag to the `channeldb`, then it will try to create a write-TX for the migration and fail. But of course with this flag any write operation will also still fail.
So you're suggesting I should call the flag `NoMigration` instead?
bhandras: > 

I see, makes sense :+1:  Yes I think I'd prefer to perhaps rename to `NoMigration` to avoid confusion.

Patch:
diff --git a/channeldb/db.go b/channeldb/db.go
index 1b56d3de3..92a49c2f8 100644
--- a/channeldb/db.go
+++ b/channeldb/db.go
@@ -263,15 +263,17 @@ func Open(dbPath string, modifiers ...OptionModifier) (*DB, error) {
 // CreateWithBackend creates channeldb instance using the passed kvdb.Backend.
 // Any necessary schemas migrations due to updates will take place as necessary.
 func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB, error) {
-	if err := initChannelDB(backend); err != nil {
-		return nil, err
-	}
-
 	opts := DefaultOptions()
 	for _, modifier := range modifiers {
 		modifier(&opts)
 	}
 
+	if !opts.NoMigration {
+		if err := initChannelDB(backend); err != nil {
+			return nil, err
+		}
+	}
+
 	chanDB := &DB{
 		Backend: backend,
 		channelStateDB: &ChannelStateDB{
@@ -291,16 +293,18 @@ func CreateWithBackend(backend kvdb.Backend, modifiers ...OptionModifier) (*DB,
 	chanDB.graph, err = NewChannelGraph(
 		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
 		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
-		opts.UseGraphCache,
+		opts.UseGraphCache, opts.NoMigration,
 	)
 	if err != nil {
 		return nil, err
 	}
 
 	// Synchronize the version of database and apply migrations if needed.
-	if err := chanDB.syncVersions(dbVersions); err != nil {
-		backend.Close()
-		return nil, err
+	if !opts.NoMigration {
+		if err := chanDB.syncVersions(dbVersions); err != nil {
+			backend.Close()
+			return nil, err
+		}
 	}
 
 	return chanDB, nil
diff --git a/channeldb/graph.go b/channeldb/graph.go
index 118f1882d..fc35dd659 100644
--- a/channeldb/graph.go
+++ b/channeldb/graph.go
@@ -189,10 +189,12 @@ type ChannelGraph struct {
 // returned instance has its own unique reject cache and channel cache.
 func NewChannelGraph(db kvdb.Backend, rejectCacheSize, chanCacheSize int,
 	batchCommitInterval time.Duration, preAllocCacheNumNodes int,
-	useGraphCache bool) (*ChannelGraph, error) {
+	useGraphCache, noMigrations bool) (*ChannelGraph, error) {
 
-	if err := initChannelGraph(db); err != nil {
-		return nil, err
+	if !noMigrations {
+		if err := initChannelGraph(db); err != nil {
+			return nil, err
+		}
 	}
 
 	g := &ChannelGraph{
diff --git a/channeldb/options.go b/channeldb/options.go
index bc53bb47e..8b8621bcf 100644
--- a/channeldb/options.go
+++ b/channeldb/options.go
@@ -50,6 +50,11 @@ type Options struct {
 	// path finding.
 	UseGraphCache bool
 
+	// NoMigration specifies that underlying backend was opened in read-only
+	// mode and migrations shouldn't be performed. This can be useful for
+	// applications that use the channeldb package as a library.
+	NoMigration bool
+
 	// clock is the time source used by the database.
 	clock clock.Clock
 
@@ -71,6 +76,7 @@ func DefaultOptions() Options {
 		ChannelCacheSize:      DefaultChannelCacheSize,
 		PreAllocCacheNumNodes: DefaultPreAllocCacheNumNodes,
 		UseGraphCache:         true,
+		NoMigration:           false,
 		clock:                 clock.NewDefaultClock(),
 	}
 }
@@ -136,6 +142,14 @@ func OptionSetBatchCommitInterval(interval time.Duration) OptionModifier {
 	}
 }
 
+// OptionNoMigration allows the database to be opened in read only mode by
+// disabling migrations.
+func OptionNoMigration(b bool) OptionModifier {
+	return func(o *Options) {
+		o.NoMigration = b
+	}
+}
+
 // OptionClock sets a non-default clock dependency.
 func OptionClock(clock clock.Clock) OptionModifier {
 	return func(o *Options) {
diff --git a/docs/release-notes/release-notes-0.15.0.md b/docs/release-notes/release-notes-0.15.0.md
index 44a76095f..f17c6042e 100644
--- a/docs/release-notes/release-notes-0.15.0.md
+++ b/docs/release-notes/release-notes-0.15.0.md
@@ -16,6 +16,9 @@
   for running lnd alongside a bitcoind service is now provided in
   `contrib/init/lnd.service`.
 
+* [Allow disabling migrations if the database backend passed to `channeldb` was
+  opened in read-only mode](https://github.com/lightningnetwork/lnd/pull/6084).
+
 ## Code Health
 
 ### Code cleanup, refactor, typo fixes


Test patch:
diff --git a/channeldb/graph_test.go b/channeldb/graph_test.go
index 36ee20095..22ca0d4a6 100644
--- a/channeldb/graph_test.go
+++ b/channeldb/graph_test.go
@@ -77,7 +77,7 @@ func MakeTestGraph(modifiers ...OptionModifier) (*ChannelGraph, func(), error) {
 	graph, err := NewChannelGraph(
 		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
 		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
-		true,
+		true, false,
 	)
 	if err != nil {
 		backendCleanup()
diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go
index dbe38475d..ffb927c8d 100644
--- a/routing/pathfind_test.go
+++ b/routing/pathfind_test.go
@@ -175,7 +175,7 @@ func makeTestGraph(useCache bool) (*channeldb.ChannelGraph, kvdb.Backend,
 	graph, err := channeldb.NewChannelGraph(
 		backend, opts.RejectCacheSize, opts.ChannelCacheSize,
 		opts.BatchCommitInterval, opts.PreAllocCacheNumNodes,
-		useCache,
+		useCache, false,
 	)
 	if err != nil {
 		cleanUp()