github.com/decred/dcrlnd@v0.7.6/lncfg/db.go (about)

     1  package lncfg
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/decred/dcrlnd/kvdb"
     9  	"github.com/decred/dcrlnd/kvdb/etcd"
    10  	"github.com/decred/dcrlnd/kvdb/postgres"
    11  	walletloader "github.com/decred/dcrlnd/lnwallet/dcrwallet/loader"
    12  )
    13  
    14  const (
    15  	channelDBName     = "channel.db"
    16  	macaroonDBName    = "macaroons.db"
    17  	decayedLogDbName  = "sphinxreplay.db"
    18  	towerClientDBName = "wtclient.db"
    19  	towerServerDBName = "watchtower.db"
    20  
    21  	BoltBackend                = "bolt"
    22  	EtcdBackend                = "etcd"
    23  	PostgresBackend            = "postgres"
    24  	DefaultBatchCommitInterval = 500 * time.Millisecond
    25  
    26  	defaultPostgresMaxConnections = 50
    27  
    28  	// NSChannelDB is the namespace name that we use for the combined graph
    29  	// and channel state DB.
    30  	NSChannelDB = "channeldb"
    31  
    32  	// NSMacaroonDB is the namespace name that we use for the macaroon DB.
    33  	NSMacaroonDB = "macaroondb"
    34  
    35  	// NSDecayedLogDB is the namespace name that we use for the sphinx
    36  	// replay a.k.a. decayed log DB.
    37  	NSDecayedLogDB = "decayedlogdb"
    38  
    39  	// NSTowerClientDB is the namespace name that we use for the watchtower
    40  	// client DB.
    41  	NSTowerClientDB = "towerclientdb"
    42  
    43  	// NSTowerServerDB is the namespace name that we use for the watchtower
    44  	// server DB.
    45  	NSTowerServerDB = "towerserverdb"
    46  
    47  	// NSWalletDB is the namespace name that we use for the wallet DB.
    48  	NSWalletDB = "walletdb"
    49  )
    50  
    51  // DB holds database configuration for LND.
    52  type DB struct {
    53  	Backend string `long:"backend" description:"The selected database backend."`
    54  
    55  	BatchCommitInterval time.Duration `long:"batch-commit-interval" description:"The maximum duration the channel graph batch schedulers will wait before attempting to commit a batch of pending updates. This can be tradeoff database contenion for commit latency."`
    56  
    57  	Etcd *etcd.Config `group:"etcd" namespace:"etcd" description:"Etcd settings."`
    58  
    59  	Bolt *kvdb.BoltConfig `group:"bolt" namespace:"bolt" description:"Bolt settings."`
    60  
    61  	Postgres *postgres.Config `group:"postgres" namespace:"postgres" description:"Postgres settings."`
    62  
    63  	NoGraphCache bool `long:"no-graph-cache" description:"Don't use the in-memory graph cache for path finding. Much slower but uses less RAM. Can only be used with a bolt database backend."`
    64  }
    65  
    66  // DefaultDB creates and returns a new default DB config.
    67  func DefaultDB() *DB {
    68  	return &DB{
    69  		Backend:             BoltBackend,
    70  		BatchCommitInterval: DefaultBatchCommitInterval,
    71  		Bolt: &kvdb.BoltConfig{
    72  			NoFreelistSync:    true,
    73  			AutoCompactMinAge: kvdb.DefaultBoltAutoCompactMinAge,
    74  			DBTimeout:         kvdb.DefaultDBTimeout,
    75  		},
    76  		Etcd: &etcd.Config{
    77  			// Allow at most 32 MiB messages by default.
    78  			MaxMsgSize: 32768 * 1024,
    79  		},
    80  		Postgres: &postgres.Config{
    81  			MaxConnections: defaultPostgresMaxConnections,
    82  		},
    83  	}
    84  }
    85  
    86  // Validate validates the DB config.
    87  func (db *DB) Validate() error {
    88  	switch db.Backend {
    89  	case BoltBackend:
    90  	case PostgresBackend:
    91  		if db.Postgres.Dsn == "" {
    92  			return fmt.Errorf("postgres dsn must be set")
    93  		}
    94  
    95  	case EtcdBackend:
    96  		if !db.Etcd.Embedded && db.Etcd.Host == "" {
    97  			return fmt.Errorf("etcd host must be set")
    98  		}
    99  
   100  	default:
   101  		return fmt.Errorf("unknown backend, must be either '%v' or "+
   102  			"'%v'", BoltBackend, EtcdBackend)
   103  	}
   104  
   105  	// The path finding uses a manual read transaction that's open for a
   106  	// potentially long time. That works fine with the locking model of
   107  	// bbolt but can lead to locks or rolled back transactions with etcd or
   108  	// postgres. And since we already have a smaller memory footprint for
   109  	// remote database setups (due to not needing to memory-map the bbolt DB
   110  	// files), we can keep the graph in memory instead. But for mobile
   111  	// devices the tradeoff between a smaller memory footprint and the
   112  	// longer time needed for path finding might be a desirable one.
   113  	if db.NoGraphCache && db.Backend != BoltBackend {
   114  		return fmt.Errorf("cannot use no-graph-cache with database "+
   115  			"backend '%v'", db.Backend)
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  // Init should be called upon start to pre-initialize database access dependent
   122  // on configuration.
   123  func (db *DB) Init(ctx context.Context, dbPath string) error {
   124  	// Start embedded etcd server if requested.
   125  	switch {
   126  	case db.Backend == EtcdBackend && db.Etcd.Embedded:
   127  		cfg, _, err := kvdb.StartEtcdTestBackend(
   128  			dbPath, db.Etcd.EmbeddedClientPort,
   129  			db.Etcd.EmbeddedPeerPort, db.Etcd.EmbeddedLogFile,
   130  		)
   131  		if err != nil {
   132  			return err
   133  		}
   134  
   135  		// Override the original config with the config for
   136  		// the embedded instance.
   137  		db.Etcd = cfg
   138  
   139  	case db.Backend == PostgresBackend:
   140  		postgres.Init(db.Postgres.MaxConnections)
   141  	}
   142  
   143  	return nil
   144  }
   145  
   146  // DatabaseBackends is a two-tuple that holds the set of active database
   147  // backends for the daemon. The two backends we expose are the graph database
   148  // backend, and the channel state backend.
   149  type DatabaseBackends struct {
   150  	// GraphDB points to the database backend that contains the less
   151  	// critical data that is accessed often, such as the channel graph and
   152  	// chain height hints.
   153  	GraphDB kvdb.Backend
   154  
   155  	// ChanStateDB points to a possibly networked replicated backend that
   156  	// contains the critical channel state related data.
   157  	ChanStateDB kvdb.Backend
   158  
   159  	// HeightHintDB points to a possibly networked replicated backend that
   160  	// contains the chain height hint related data.
   161  	HeightHintDB kvdb.Backend
   162  
   163  	// MacaroonDB points to a database backend that stores the macaroon root
   164  	// keys.
   165  	MacaroonDB kvdb.Backend
   166  
   167  	// DecayedLogDB points to a database backend that stores the decayed log
   168  	// data.
   169  	DecayedLogDB kvdb.Backend
   170  
   171  	// TowerClientDB points to a database backend that stores the watchtower
   172  	// client data. This might be nil if the watchtower client is disabled.
   173  	TowerClientDB kvdb.Backend
   174  
   175  	// TowerServerDB points to a database backend that stores the watchtower
   176  	// server data. This might be nil if the watchtower server is disabled.
   177  	TowerServerDB kvdb.Backend
   178  
   179  	// WalletDB is an option that instructs the wallet loader where to load
   180  	// the underlying wallet database from.
   181  	WalletDB walletloader.LoaderOption
   182  
   183  	// Remote indicates whether the database backends are remote, possibly
   184  	// replicated instances or local bbolt backed databases.
   185  	Remote bool
   186  
   187  	// CloseFuncs is a map of close functions for each of the initialized
   188  	// DB backends keyed by their namespace name.
   189  	CloseFuncs map[string]func() error
   190  }
   191  
   192  // GetBackends returns a set of kvdb.Backends as set in the DB config.
   193  func (db *DB) GetBackends(ctx context.Context, chanDBPath,
   194  	walletDBPath, towerServerDBPath string, towerClientEnabled,
   195  	towerServerEnabled bool) (*DatabaseBackends, error) {
   196  
   197  	// We keep track of all the kvdb backends we actually open and return a
   198  	// reference to their close function so they can be cleaned up properly
   199  	// on error or shutdown.
   200  	closeFuncs := make(map[string]func() error)
   201  
   202  	// If we need to return early because of an error, we invoke any close
   203  	// function that has been initialized so far.
   204  	returnEarly := true
   205  	defer func() {
   206  		if !returnEarly {
   207  			return
   208  		}
   209  
   210  		for _, closeFunc := range closeFuncs {
   211  			_ = closeFunc()
   212  		}
   213  	}()
   214  
   215  	switch db.Backend {
   216  	case EtcdBackend:
   217  		// As long as the graph data, channel state and height hint
   218  		// cache are all still in the channel.db file in bolt, we
   219  		// replicate the same behavior here and use the same etcd
   220  		// backend for those three sub DBs. But we namespace it properly
   221  		// to make such a split even easier in the future. This will
   222  		// break lnd for users that ran on etcd with 0.13.x since that
   223  		// code used the root namespace. We assume that nobody used etcd
   224  		// for mainnet just yet since that feature was clearly marked as
   225  		// experimental in 0.13.x.
   226  		etcdBackend, err := kvdb.Open(
   227  			kvdb.EtcdBackendName, ctx,
   228  			db.Etcd.CloneWithSubNamespace(NSChannelDB),
   229  		)
   230  		if err != nil {
   231  			return nil, fmt.Errorf("error opening etcd DB: %v", err)
   232  		}
   233  		closeFuncs[NSChannelDB] = etcdBackend.Close
   234  
   235  		etcdMacaroonBackend, err := kvdb.Open(
   236  			kvdb.EtcdBackendName, ctx,
   237  			db.Etcd.CloneWithSubNamespace(NSMacaroonDB),
   238  		)
   239  		if err != nil {
   240  			return nil, fmt.Errorf("error opening etcd macaroon "+
   241  				"DB: %v", err)
   242  		}
   243  		closeFuncs[NSMacaroonDB] = etcdMacaroonBackend.Close
   244  
   245  		etcdDecayedLogBackend, err := kvdb.Open(
   246  			kvdb.EtcdBackendName, ctx,
   247  			db.Etcd.CloneWithSubNamespace(NSDecayedLogDB),
   248  		)
   249  		if err != nil {
   250  			return nil, fmt.Errorf("error opening etcd decayed "+
   251  				"log DB: %v", err)
   252  		}
   253  		closeFuncs[NSDecayedLogDB] = etcdDecayedLogBackend.Close
   254  
   255  		etcdTowerClientBackend, err := kvdb.Open(
   256  			kvdb.EtcdBackendName, ctx,
   257  			db.Etcd.CloneWithSubNamespace(NSTowerClientDB),
   258  		)
   259  		if err != nil {
   260  			return nil, fmt.Errorf("error opening etcd tower "+
   261  				"client DB: %v", err)
   262  		}
   263  		closeFuncs[NSTowerClientDB] = etcdTowerClientBackend.Close
   264  
   265  		etcdTowerServerBackend, err := kvdb.Open(
   266  			kvdb.EtcdBackendName, ctx,
   267  			db.Etcd.CloneWithSubNamespace(NSTowerServerDB),
   268  		)
   269  		if err != nil {
   270  			return nil, fmt.Errorf("error opening etcd tower "+
   271  				"server DB: %v", err)
   272  		}
   273  		closeFuncs[NSTowerServerDB] = etcdTowerServerBackend.Close
   274  
   275  		etcdWalletBackend, err := kvdb.Open(
   276  			kvdb.EtcdBackendName, ctx,
   277  			db.Etcd.
   278  				CloneWithSubNamespace(NSWalletDB).
   279  				CloneWithSingleWriter(),
   280  		)
   281  		if err != nil {
   282  			return nil, fmt.Errorf("error opening etcd macaroon "+
   283  				"DB: %v", err)
   284  		}
   285  		closeFuncs[NSWalletDB] = etcdWalletBackend.Close
   286  
   287  		returnEarly = false
   288  		return &DatabaseBackends{
   289  			GraphDB:       etcdBackend,
   290  			ChanStateDB:   etcdBackend,
   291  			HeightHintDB:  etcdBackend,
   292  			MacaroonDB:    etcdMacaroonBackend,
   293  			DecayedLogDB:  etcdDecayedLogBackend,
   294  			TowerClientDB: etcdTowerClientBackend,
   295  			TowerServerDB: etcdTowerServerBackend,
   296  			// The wallet loader will attempt to use/create the
   297  			// wallet in the replicated remote DB if we're running
   298  			// in a clustered environment. This will ensure that all
   299  			// members of the cluster have access to the same wallet
   300  			// state.
   301  			WalletDB: walletloader.LoaderWithExternalWalletDB(
   302  				etcdBackend,
   303  			),
   304  			Remote:     true,
   305  			CloseFuncs: closeFuncs,
   306  		}, nil
   307  
   308  	case PostgresBackend:
   309  		postgresBackend, err := kvdb.Open(
   310  			kvdb.PostgresBackendName, ctx,
   311  			db.Postgres, NSChannelDB,
   312  		)
   313  		if err != nil {
   314  			return nil, fmt.Errorf("error opening postgres graph "+
   315  				"DB: %v", err)
   316  		}
   317  		closeFuncs[NSChannelDB] = postgresBackend.Close
   318  
   319  		postgresMacaroonBackend, err := kvdb.Open(
   320  			kvdb.PostgresBackendName, ctx,
   321  			db.Postgres, NSMacaroonDB,
   322  		)
   323  		if err != nil {
   324  			return nil, fmt.Errorf("error opening postgres "+
   325  				"macaroon DB: %v", err)
   326  		}
   327  		closeFuncs[NSMacaroonDB] = postgresMacaroonBackend.Close
   328  
   329  		postgresDecayedLogBackend, err := kvdb.Open(
   330  			kvdb.PostgresBackendName, ctx,
   331  			db.Postgres, NSDecayedLogDB,
   332  		)
   333  		if err != nil {
   334  			return nil, fmt.Errorf("error opening postgres "+
   335  				"decayed log DB: %v", err)
   336  		}
   337  		closeFuncs[NSDecayedLogDB] = postgresDecayedLogBackend.Close
   338  
   339  		postgresTowerClientBackend, err := kvdb.Open(
   340  			kvdb.PostgresBackendName, ctx,
   341  			db.Postgres, NSTowerClientDB,
   342  		)
   343  		if err != nil {
   344  			return nil, fmt.Errorf("error opening postgres tower "+
   345  				"client DB: %v", err)
   346  		}
   347  		closeFuncs[NSTowerClientDB] = postgresTowerClientBackend.Close
   348  
   349  		postgresTowerServerBackend, err := kvdb.Open(
   350  			kvdb.PostgresBackendName, ctx,
   351  			db.Postgres, NSTowerServerDB,
   352  		)
   353  		if err != nil {
   354  			return nil, fmt.Errorf("error opening postgres tower "+
   355  				"server DB: %v", err)
   356  		}
   357  		closeFuncs[NSTowerServerDB] = postgresTowerServerBackend.Close
   358  
   359  		postgresWalletBackend, err := kvdb.Open(
   360  			kvdb.PostgresBackendName, ctx,
   361  			db.Postgres, NSWalletDB,
   362  		)
   363  		if err != nil {
   364  			return nil, fmt.Errorf("error opening postgres macaroon "+
   365  				"DB: %v", err)
   366  		}
   367  		closeFuncs[NSWalletDB] = postgresWalletBackend.Close
   368  
   369  		returnEarly = false
   370  		return &DatabaseBackends{
   371  			GraphDB:       postgresBackend,
   372  			ChanStateDB:   postgresBackend,
   373  			HeightHintDB:  postgresBackend,
   374  			MacaroonDB:    postgresMacaroonBackend,
   375  			DecayedLogDB:  postgresDecayedLogBackend,
   376  			TowerClientDB: postgresTowerClientBackend,
   377  			TowerServerDB: postgresTowerServerBackend,
   378  			// The wallet loader will attempt to use/create the
   379  			// wallet in the replicated remote DB if we're running
   380  			// in a clustered environment. This will ensure that all
   381  			// members of the cluster have access to the same wallet
   382  			// state.
   383  			WalletDB: walletloader.LoaderWithExternalWalletDB(
   384  				postgresWalletBackend,
   385  			),
   386  			Remote:     true,
   387  			CloseFuncs: closeFuncs,
   388  		}, nil
   389  	}
   390  
   391  	// We're using all bbolt based databases by default.
   392  	boltBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
   393  		DBPath:            chanDBPath,
   394  		DBFileName:        channelDBName,
   395  		DBTimeout:         db.Bolt.DBTimeout,
   396  		NoFreelistSync:    db.Bolt.NoFreelistSync,
   397  		AutoCompact:       db.Bolt.AutoCompact,
   398  		AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
   399  	})
   400  	if err != nil {
   401  		return nil, fmt.Errorf("error opening bolt DB: %v", err)
   402  	}
   403  	closeFuncs[NSChannelDB] = boltBackend.Close
   404  
   405  	macaroonBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
   406  		DBPath:            walletDBPath,
   407  		DBFileName:        macaroonDBName,
   408  		DBTimeout:         db.Bolt.DBTimeout,
   409  		NoFreelistSync:    db.Bolt.NoFreelistSync,
   410  		AutoCompact:       db.Bolt.AutoCompact,
   411  		AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
   412  	})
   413  	if err != nil {
   414  		return nil, fmt.Errorf("error opening macaroon DB: %v", err)
   415  	}
   416  	closeFuncs[NSMacaroonDB] = macaroonBackend.Close
   417  
   418  	decayedLogBackend, err := kvdb.GetBoltBackend(&kvdb.BoltBackendConfig{
   419  		DBPath:            chanDBPath,
   420  		DBFileName:        decayedLogDbName,
   421  		DBTimeout:         db.Bolt.DBTimeout,
   422  		NoFreelistSync:    db.Bolt.NoFreelistSync,
   423  		AutoCompact:       db.Bolt.AutoCompact,
   424  		AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
   425  	})
   426  	if err != nil {
   427  		return nil, fmt.Errorf("error opening decayed log DB: %v", err)
   428  	}
   429  	closeFuncs[NSDecayedLogDB] = decayedLogBackend.Close
   430  
   431  	// The tower client is optional and might not be enabled by the user. We
   432  	// handle it being nil properly in the main server.
   433  	var towerClientBackend kvdb.Backend
   434  	if towerClientEnabled {
   435  		towerClientBackend, err = kvdb.GetBoltBackend(
   436  			&kvdb.BoltBackendConfig{
   437  				DBPath:            chanDBPath,
   438  				DBFileName:        towerClientDBName,
   439  				DBTimeout:         db.Bolt.DBTimeout,
   440  				NoFreelistSync:    db.Bolt.NoFreelistSync,
   441  				AutoCompact:       db.Bolt.AutoCompact,
   442  				AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
   443  			},
   444  		)
   445  		if err != nil {
   446  			return nil, fmt.Errorf("error opening tower client "+
   447  				"DB: %v", err)
   448  		}
   449  		closeFuncs[NSTowerClientDB] = towerClientBackend.Close
   450  	}
   451  
   452  	// The tower server is optional and might not be enabled by the user. We
   453  	// handle it being nil properly in the main server.
   454  	var towerServerBackend kvdb.Backend
   455  	if towerServerEnabled {
   456  		towerServerBackend, err = kvdb.GetBoltBackend(
   457  			&kvdb.BoltBackendConfig{
   458  				DBPath:            towerServerDBPath,
   459  				DBFileName:        towerServerDBName,
   460  				DBTimeout:         db.Bolt.DBTimeout,
   461  				NoFreelistSync:    db.Bolt.NoFreelistSync,
   462  				AutoCompact:       db.Bolt.AutoCompact,
   463  				AutoCompactMinAge: db.Bolt.AutoCompactMinAge,
   464  			},
   465  		)
   466  		if err != nil {
   467  			return nil, fmt.Errorf("error opening tower server "+
   468  				"DB: %v", err)
   469  		}
   470  		closeFuncs[NSTowerServerDB] = towerServerBackend.Close
   471  	}
   472  
   473  	returnEarly = false
   474  	return &DatabaseBackends{
   475  		GraphDB:       boltBackend,
   476  		ChanStateDB:   boltBackend,
   477  		HeightHintDB:  boltBackend,
   478  		MacaroonDB:    macaroonBackend,
   479  		DecayedLogDB:  decayedLogBackend,
   480  		TowerClientDB: towerClientBackend,
   481  		TowerServerDB: towerServerBackend,
   482  		// When "running locally", LND will use the bbolt wallet.db to
   483  		// store the wallet located in the chain data dir, parametrized
   484  		// by the active network.
   485  		WalletDB: walletloader.LoaderWithLocalWalletDB(
   486  			walletDBPath, db.Bolt.NoFreelistSync, db.Bolt.DBTimeout,
   487  		),
   488  		CloseFuncs: closeFuncs,
   489  	}, nil
   490  }
   491  
   492  // Compile-time constraint to ensure Workers implements the Validator interface.
   493  var _ Validator = (*DB)(nil)