firewalldb+log: add main DB structure

This commit is contained in:
Elle Mouton 2022-07-08 13:50:08 +02:00
parent 69516ec675
commit eaffa4a74a
No known key found for this signature in database
GPG key ID: D7D916376026F177
4 changed files with 262 additions and 0 deletions

112
firewalldb/db.go Normal file
View file

@ -0,0 +1,112 @@
package firewalldb
import (
"encoding/binary"
"fmt"
"os"
"path/filepath"
"time"
"go.etcd.io/bbolt"
)
const (
// DBFilename is the default filename of the rules' database.
DBFilename = "rules.db"
// dbFilePermission is the default permission the rules' database file
// is created with.
dbFilePermission = 0600
// DefaultRulesDBTimeout is the default maximum time we wait for the
// db bbolt database to be opened. If the database is already
// opened by another process, the unique lock cannot be obtained. With
// the timeout we error out after the given time instead of just
// blocking for forever.
DefaultRulesDBTimeout = 5 * time.Second
)
var (
// byteOrder is the default byte order we'll use for serialization
// within the database.
byteOrder = binary.BigEndian
)
// DB is a bolt-backed persistent store.
type DB struct {
*bbolt.DB
}
// NewDB creates a new bolt database that can be found at the given directory.
func NewDB(dir, fileName string) (*DB, error) {
firstInit := false
path := filepath.Join(dir, fileName)
// If the database file does not exist yet, create its directory.
if !fileExists(path) {
if err := os.MkdirAll(dir, 0700); err != nil {
return nil, err
}
firstInit = true
}
db, err := initDB(path, firstInit)
if err != nil {
return nil, err
}
// Attempt to sync the database's current version with the latest known
// version available.
if err := syncVersions(db); err != nil {
return nil, err
}
return &DB{DB: db}, nil
}
// fileExists reports whether the named file or directory exists.
func fileExists(path string) bool {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// initDB initializes all the required top-level buckets for the database.
func initDB(filepath string, firstInit bool) (*bbolt.DB, error) {
db, err := bbolt.Open(filepath, dbFilePermission, &bbolt.Options{
Timeout: DefaultRulesDBTimeout,
})
if err == bbolt.ErrTimeout {
return nil, fmt.Errorf("error while trying to open %s: timed "+
"out after %v when trying to obtain exclusive lock",
filepath, DefaultRulesDBTimeout)
}
if err != nil {
return nil, err
}
err = db.Update(func(tx *bbolt.Tx) error {
if firstInit {
metadataBucket, err := tx.CreateBucketIfNotExists(
metadataBucketKey,
)
if err != nil {
return err
}
err = setDBVersion(metadataBucket, latestDBVersion)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
return db, nil
}

25
firewalldb/log.go Normal file
View file

@ -0,0 +1,25 @@
package firewalldb
import (
"github.com/btcsuite/btclog"
"github.com/lightningnetwork/lnd/build"
)
const Subsystem = "FWDB"
// log is a logger that is initialized with no output filters. This
// means the package will not perform any logging by default until the caller
// requests it.
var log btclog.Logger
// The default amount of logging is none.
func init() {
UseLogger(build.NewSubLogger(Subsystem, nil))
}
// UseLogger uses a specified Logger to output package logging info.
// This should be used in preference to SetLogWriter if the caller is also
// using btclog.
func UseLogger(logger btclog.Logger) {
log = logger
}

121
firewalldb/metadata.go Normal file
View file

@ -0,0 +1,121 @@
package firewalldb
import (
"errors"
"fmt"
"go.etcd.io/bbolt"
)
// migration is a function which takes a prior outdated version of the database
// instance and mutates the key/bucket structure to arrive at a more up-to-date
// version of the database.
type migration func(tx *bbolt.Tx) error
var (
// metadataBucketKey stores all the metadata concerning the state of the
// database.
metadataBucketKey = []byte("metadata")
// dbVersionKey is the key used for storing/retrieving the current
// database version.
dbVersionKey = []byte("version")
// ErrDBReversion is returned when detecting an attempt to revert to a
// prior database version.
ErrDBReversion = errors.New("cannot revert to prior version")
// dbVersions is storing all versions of database. If the current
// version of the database doesn't match the latest version this list
// will be used for retrieving all migration function that are need to
// apply to the current db.
dbVersions []migration
latestDBVersion = uint32(len(dbVersions))
)
// getDBVersion retrieves the current database version.
func getDBVersion(bucket *bbolt.Bucket) (uint32, error) {
versionBytes := bucket.Get(dbVersionKey)
if versionBytes == nil {
return 0, errors.New("database version not found")
}
return byteOrder.Uint32(versionBytes), nil
}
// setDBVersion updates the current database version.
func setDBVersion(bucket *bbolt.Bucket, version uint32) error {
var b [4]byte
byteOrder.PutUint32(b[:], version)
return bucket.Put(dbVersionKey, b[:])
}
// getBucket retrieves the bucket with the given key.
func getBucket(tx *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {
bucket := tx.Bucket(key)
if bucket == nil {
return nil, fmt.Errorf("bucket \"%v\" does not exist",
string(key))
}
return bucket, nil
}
// syncVersions function is used for safe db version synchronization. It
// applies migration functions to the current database and recovers the
// previous state of db if at least one error/panic appeared during migration.
func syncVersions(db *bbolt.DB) error {
var currentVersion uint32
err := db.View(func(tx *bbolt.Tx) error {
metadata, err := getBucket(tx, metadataBucketKey)
if err != nil {
return err
}
currentVersion, err = getDBVersion(metadata)
return err
})
if err != nil {
return err
}
log.Infof("Checking for schema update: latest_version=%v, "+
"db_version=%v", latestDBVersion, currentVersion)
switch {
// If the database reports a higher version that we are aware of, the
// user is probably trying to revert to a prior version of lnd. We fail
// here to prevent reversions and unintended corruption.
case currentVersion > latestDBVersion:
log.Errorf("Refusing to revert from db_version=%d to "+
"lower version=%d", currentVersion,
latestDBVersion)
return ErrDBReversion
// If the current database version matches the latest version number,
// then we don't need to perform any migrations.
case currentVersion == latestDBVersion:
return nil
}
log.Infof("Performing database schema migration")
// Otherwise, we execute the migrations serially within a single
// database transaction to ensure the migration is atomic.
return db.Update(func(tx *bbolt.Tx) error {
for v := currentVersion; v < latestDBVersion; v++ {
log.Infof("Applying migration #%v", v+1)
migration := dbVersions[v]
if err := migration(tx); err != nil {
log.Infof("Unable to apply migration #%v", v+1)
return err
}
}
metadata, err := getBucket(tx, metadataBucketKey)
if err != nil {
return err
}
return setDBVersion(metadata, latestDBVersion)
})
}

4
log.go
View file

@ -6,6 +6,7 @@ import (
"github.com/lightninglabs/lightning-node-connect/mailbox"
"github.com/lightninglabs/lightning-terminal/accounts"
"github.com/lightninglabs/lightning-terminal/firewall"
"github.com/lightninglabs/lightning-terminal/firewalldb"
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
"github.com/lightninglabs/lightning-terminal/session"
"github.com/lightninglabs/loop/loopd"
@ -69,6 +70,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
lnd.AddSubLogger(
root, firewall.Subsystem, intercept, firewall.UseLogger,
)
lnd.AddSubLogger(
root, firewalldb.Subsystem, intercept, firewalldb.UseLogger,
)
// Add daemon loggers to lnd's root logger.
faraday.SetupLoggers(root, intercept)