-
Notifications
You must be signed in to change notification settings - Fork 246
/
db.go
169 lines (137 loc) · 4.47 KB
/
db.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package sqlite
import (
"database/sql"
"fmt"
"os"
"github.com/pkg/errors"
_ "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
"github.com/status-im/migrate/v4"
"github.com/status-im/migrate/v4/database/sqlcipher"
bindata "github.com/status-im/migrate/v4/source/go_bindata"
mvdsmigrations "github.com/vacp2p/mvds/persistenceutil"
)
// The reduced number of kdf iterations (for performance reasons) which is
// currently used for derivation of the database key
// https://github.com/status-im/status-go/pull/1343
// https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
const ReducedKDFIterationsNumber = 3200
const InMemoryPath = ":memory:"
var migrationsTable = "status_protocol_go_" + sqlcipher.DefaultMigrationsTable
// MigrationConfig is a struct that allows to define bindata migrations.
type MigrationConfig struct {
AssetNames []string
AssetGetter func(name string) ([]byte, error)
}
// Open opens or initializes a new database for a given file path.
// MigrationConfig is optional but if provided migrations are applied automatically.
func Open(path, key string, kdfIterationNumber int) (*sql.DB, error) {
return openAndMigrate(path, key, kdfIterationNumber)
}
// OpenInMemory opens an in memory SQLite database.
// Number of KDF iterations is reduced to 0.
func OpenInMemory() (*sql.DB, error) {
return openAndMigrate(InMemoryPath, "", 0)
}
// OpenWithIter allows to open a new database with a custom number of kdf iterations.
// Higher kdf iterations number makes it slower to open the database.
func OpenWithIter(path, key string, kdfIter int) (*sql.DB, error) {
return openAndMigrate(path, key, kdfIter)
}
func open(path string, key string, kdfIter int) (*sql.DB, error) {
if path != InMemoryPath {
_, err := os.OpenFile(path, os.O_CREATE, 0600)
if err != nil {
return nil, err
}
}
db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}
keyString := fmt.Sprintf("PRAGMA key = '%s'", key)
// Disable concurrent access as not supported by the driver
db.SetMaxOpenConns(1)
if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil {
return nil, err
}
if _, err = db.Exec(keyString); err != nil {
return nil, err
}
kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter)
if _, err = db.Exec(kdfString); err != nil {
return nil, err
}
return db, nil
}
func openAndMigrate(path string, key string, kdfIter int) (*sql.DB, error) {
db, err := open(path, key, kdfIter)
if err != nil {
return nil, err
}
if err := Migrate(db); err != nil {
return nil, err
}
return db, nil
}
// applyMigrations allows to apply bindata migrations on the current *sql.DB.
// `assetNames` is a list of assets with migrations and `assetGetter` is responsible
// for returning the content of the asset with a given name.
func applyMigrations(db *sql.DB, assetNames []string, assetGetter func(name string) ([]byte, error)) error {
resources := bindata.Resource(
assetNames,
assetGetter,
)
source, err := bindata.WithInstance(resources)
if err != nil {
return errors.Wrap(err, "failed to create migration source")
}
driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{
MigrationsTable: migrationsTable,
})
if err != nil {
return errors.Wrap(err, "failed to create driver")
}
m, err := migrate.NewWithInstance(
"go-bindata",
source,
"sqlcipher",
driver,
)
if err != nil {
return errors.Wrap(err, "failed to create migration instance")
}
version, dirty, err := m.Version()
if err != nil && err != migrate.ErrNilVersion {
return errors.Wrap(err, "could not get version")
}
err = ApplyAdHocMigrations(version, dirty, m, db)
if err != nil {
return errors.Wrap(err, "failed to apply ad-hoc migrations")
}
if dirty {
err = ReplayLastMigration(version, m)
if err != nil {
return errors.Wrap(err, "failed to replay last migration")
}
}
if err = m.Up(); err != migrate.ErrNoChange {
return errors.Wrap(err, "failed to migrate")
}
return nil
}
func Migrate(database *sql.DB) error {
// Apply migrations for all components.
err := mvdsmigrations.Migrate(database)
if err != nil {
return errors.Wrap(err, "failed to apply mvds migrations")
}
migrationNames, migrationGetter, err := prepareMigrations(defaultMigrations)
if err != nil {
return errors.Wrap(err, "failed to prepare status-go/protocol migrations")
}
err = applyMigrations(database, migrationNames, migrationGetter)
if err != nil {
return errors.Wrap(err, "failed to apply status-go/protocol migrations")
}
return nil
}