Skip to content

Commit eaffa4a

Browse files
committed
firewalldb+log: add main DB structure
1 parent 69516ec commit eaffa4a

File tree

4 files changed

+262
-0
lines changed

4 files changed

+262
-0
lines changed

firewalldb/db.go

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package firewalldb
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
"time"
9+
10+
"go.etcd.io/bbolt"
11+
)
12+
13+
const (
14+
// DBFilename is the default filename of the rules' database.
15+
DBFilename = "rules.db"
16+
17+
// dbFilePermission is the default permission the rules' database file
18+
// is created with.
19+
dbFilePermission = 0600
20+
21+
// DefaultRulesDBTimeout is the default maximum time we wait for the
22+
// db bbolt database to be opened. If the database is already
23+
// opened by another process, the unique lock cannot be obtained. With
24+
// the timeout we error out after the given time instead of just
25+
// blocking for forever.
26+
DefaultRulesDBTimeout = 5 * time.Second
27+
)
28+
29+
var (
30+
// byteOrder is the default byte order we'll use for serialization
31+
// within the database.
32+
byteOrder = binary.BigEndian
33+
)
34+
35+
// DB is a bolt-backed persistent store.
36+
type DB struct {
37+
*bbolt.DB
38+
}
39+
40+
// NewDB creates a new bolt database that can be found at the given directory.
41+
func NewDB(dir, fileName string) (*DB, error) {
42+
firstInit := false
43+
path := filepath.Join(dir, fileName)
44+
45+
// If the database file does not exist yet, create its directory.
46+
if !fileExists(path) {
47+
if err := os.MkdirAll(dir, 0700); err != nil {
48+
return nil, err
49+
}
50+
firstInit = true
51+
}
52+
53+
db, err := initDB(path, firstInit)
54+
if err != nil {
55+
return nil, err
56+
}
57+
58+
// Attempt to sync the database's current version with the latest known
59+
// version available.
60+
if err := syncVersions(db); err != nil {
61+
return nil, err
62+
}
63+
64+
return &DB{DB: db}, nil
65+
}
66+
67+
// fileExists reports whether the named file or directory exists.
68+
func fileExists(path string) bool {
69+
if _, err := os.Stat(path); err != nil {
70+
if os.IsNotExist(err) {
71+
return false
72+
}
73+
}
74+
return true
75+
}
76+
77+
// initDB initializes all the required top-level buckets for the database.
78+
func initDB(filepath string, firstInit bool) (*bbolt.DB, error) {
79+
db, err := bbolt.Open(filepath, dbFilePermission, &bbolt.Options{
80+
Timeout: DefaultRulesDBTimeout,
81+
})
82+
if err == bbolt.ErrTimeout {
83+
return nil, fmt.Errorf("error while trying to open %s: timed "+
84+
"out after %v when trying to obtain exclusive lock",
85+
filepath, DefaultRulesDBTimeout)
86+
}
87+
if err != nil {
88+
return nil, err
89+
}
90+
91+
err = db.Update(func(tx *bbolt.Tx) error {
92+
if firstInit {
93+
metadataBucket, err := tx.CreateBucketIfNotExists(
94+
metadataBucketKey,
95+
)
96+
if err != nil {
97+
return err
98+
}
99+
err = setDBVersion(metadataBucket, latestDBVersion)
100+
if err != nil {
101+
return err
102+
}
103+
}
104+
105+
return nil
106+
})
107+
if err != nil {
108+
return nil, err
109+
}
110+
111+
return db, nil
112+
}

firewalldb/log.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package firewalldb
2+
3+
import (
4+
"github.com/btcsuite/btclog"
5+
"github.com/lightningnetwork/lnd/build"
6+
)
7+
8+
const Subsystem = "FWDB"
9+
10+
// log is a logger that is initialized with no output filters. This
11+
// means the package will not perform any logging by default until the caller
12+
// requests it.
13+
var log btclog.Logger
14+
15+
// The default amount of logging is none.
16+
func init() {
17+
UseLogger(build.NewSubLogger(Subsystem, nil))
18+
}
19+
20+
// UseLogger uses a specified Logger to output package logging info.
21+
// This should be used in preference to SetLogWriter if the caller is also
22+
// using btclog.
23+
func UseLogger(logger btclog.Logger) {
24+
log = logger
25+
}

firewalldb/metadata.go

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
package firewalldb
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
7+
"go.etcd.io/bbolt"
8+
)
9+
10+
// migration is a function which takes a prior outdated version of the database
11+
// instance and mutates the key/bucket structure to arrive at a more up-to-date
12+
// version of the database.
13+
type migration func(tx *bbolt.Tx) error
14+
15+
var (
16+
// metadataBucketKey stores all the metadata concerning the state of the
17+
// database.
18+
metadataBucketKey = []byte("metadata")
19+
20+
// dbVersionKey is the key used for storing/retrieving the current
21+
// database version.
22+
dbVersionKey = []byte("version")
23+
24+
// ErrDBReversion is returned when detecting an attempt to revert to a
25+
// prior database version.
26+
ErrDBReversion = errors.New("cannot revert to prior version")
27+
28+
// dbVersions is storing all versions of database. If the current
29+
// version of the database doesn't match the latest version this list
30+
// will be used for retrieving all migration function that are need to
31+
// apply to the current db.
32+
dbVersions []migration
33+
34+
latestDBVersion = uint32(len(dbVersions))
35+
)
36+
37+
// getDBVersion retrieves the current database version.
38+
func getDBVersion(bucket *bbolt.Bucket) (uint32, error) {
39+
versionBytes := bucket.Get(dbVersionKey)
40+
if versionBytes == nil {
41+
return 0, errors.New("database version not found")
42+
}
43+
return byteOrder.Uint32(versionBytes), nil
44+
}
45+
46+
// setDBVersion updates the current database version.
47+
func setDBVersion(bucket *bbolt.Bucket, version uint32) error {
48+
var b [4]byte
49+
byteOrder.PutUint32(b[:], version)
50+
return bucket.Put(dbVersionKey, b[:])
51+
}
52+
53+
// getBucket retrieves the bucket with the given key.
54+
func getBucket(tx *bbolt.Tx, key []byte) (*bbolt.Bucket, error) {
55+
bucket := tx.Bucket(key)
56+
if bucket == nil {
57+
return nil, fmt.Errorf("bucket \"%v\" does not exist",
58+
string(key))
59+
}
60+
return bucket, nil
61+
}
62+
63+
// syncVersions function is used for safe db version synchronization. It
64+
// applies migration functions to the current database and recovers the
65+
// previous state of db if at least one error/panic appeared during migration.
66+
func syncVersions(db *bbolt.DB) error {
67+
var currentVersion uint32
68+
err := db.View(func(tx *bbolt.Tx) error {
69+
metadata, err := getBucket(tx, metadataBucketKey)
70+
if err != nil {
71+
return err
72+
}
73+
currentVersion, err = getDBVersion(metadata)
74+
return err
75+
})
76+
if err != nil {
77+
return err
78+
}
79+
80+
log.Infof("Checking for schema update: latest_version=%v, "+
81+
"db_version=%v", latestDBVersion, currentVersion)
82+
83+
switch {
84+
// If the database reports a higher version that we are aware of, the
85+
// user is probably trying to revert to a prior version of lnd. We fail
86+
// here to prevent reversions and unintended corruption.
87+
case currentVersion > latestDBVersion:
88+
log.Errorf("Refusing to revert from db_version=%d to "+
89+
"lower version=%d", currentVersion,
90+
latestDBVersion)
91+
92+
return ErrDBReversion
93+
94+
// If the current database version matches the latest version number,
95+
// then we don't need to perform any migrations.
96+
case currentVersion == latestDBVersion:
97+
return nil
98+
}
99+
100+
log.Infof("Performing database schema migration")
101+
102+
// Otherwise, we execute the migrations serially within a single
103+
// database transaction to ensure the migration is atomic.
104+
return db.Update(func(tx *bbolt.Tx) error {
105+
for v := currentVersion; v < latestDBVersion; v++ {
106+
log.Infof("Applying migration #%v", v+1)
107+
108+
migration := dbVersions[v]
109+
if err := migration(tx); err != nil {
110+
log.Infof("Unable to apply migration #%v", v+1)
111+
return err
112+
}
113+
}
114+
115+
metadata, err := getBucket(tx, metadataBucketKey)
116+
if err != nil {
117+
return err
118+
}
119+
return setDBVersion(metadata, latestDBVersion)
120+
})
121+
}

log.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/lightninglabs/lightning-node-connect/mailbox"
77
"github.com/lightninglabs/lightning-terminal/accounts"
88
"github.com/lightninglabs/lightning-terminal/firewall"
9+
"github.com/lightninglabs/lightning-terminal/firewalldb"
910
mid "github.com/lightninglabs/lightning-terminal/rpcmiddleware"
1011
"github.com/lightninglabs/lightning-terminal/session"
1112
"github.com/lightninglabs/loop/loopd"
@@ -69,6 +70,9 @@ func SetupLoggers(root *build.RotatingLogWriter, intercept signal.Interceptor) {
6970
lnd.AddSubLogger(
7071
root, firewall.Subsystem, intercept, firewall.UseLogger,
7172
)
73+
lnd.AddSubLogger(
74+
root, firewalldb.Subsystem, intercept, firewalldb.UseLogger,
75+
)
7276

7377
// Add daemon loggers to lnd's root logger.
7478
faraday.SetupLoggers(root, intercept)

0 commit comments

Comments
 (0)