feat(migration): sqlite migration improvements.

Some functions split to be more cohesive, custom PostSteps are
stored as pointers to allow their retrieval and parameters passing
in runtime if needed (some extra work is dropped and TBD
when needed)
This commit is contained in:
Ivan Belyakov 2023-08-08 08:55:13 +02:00 committed by IvanBelyakoff
parent d106b449b6
commit aa3d33a58f
8 changed files with 56 additions and 30 deletions

View File

@ -20,7 +20,7 @@ import (
const nodeCfgMigrationDate = 1640111208 const nodeCfgMigrationDate = 1640111208
var customSteps = []sqlite.PostStep{ var customSteps = []*sqlite.PostStep{
{Version: 1674136690, CustomMigration: migrateEnsUsernames}, {Version: 1674136690, CustomMigration: migrateEnsUsernames},
{Version: 1686048341, CustomMigration: migrateWalletJSONBlobs, RollBackVersion: 1686041510}, {Version: 1686048341, CustomMigration: migrateWalletJSONBlobs, RollBackVersion: 1686041510},
{Version: 1687193315, CustomMigration: migrateWalletTransferFromToAddresses, RollBackVersion: 1686825075}, {Version: 1687193315, CustomMigration: migrateWalletTransferFromToAddresses, RollBackVersion: 1686825075},

View File

@ -182,7 +182,7 @@ func TestMigrateWalletJsonBlobs(t *testing.T) {
err = insertTestTransaction(9, uniswapV3TxTestData, uniswapV3ReceiptTestData, uniswapV3LogTestData, false) err = insertTestTransaction(9, uniswapV3TxTestData, uniswapV3ReceiptTestData, uniswapV3LogTestData, false)
require.NoError(t, err) require.NoError(t, err)
failMigrationSteps := []sqlite.PostStep{ failMigrationSteps := []*sqlite.PostStep{
{ {
Version: customSteps[1].Version, Version: customSteps[1].Version,
CustomMigration: func(sqlTx *sql.Tx) error { CustomMigration: func(sqlTx *sql.Tx) error {

View File

@ -10,7 +10,7 @@ import (
// Migrate applies migrations. // Migrate applies migrations.
// see Migrate in vendor/status-go/sqlite/migrate.go // see Migrate in vendor/status-go/sqlite/migrate.go
func Migrate(db *sql.DB, customSteps []sqlite.PostStep) error { func Migrate(db *sql.DB, customSteps []*sqlite.PostStep) error {
return sqlite.Migrate(db, bindata.Resource( return sqlite.Migrate(db, bindata.Resource(
AssetNames(), AssetNames(),
func(name string) ([]byte, error) { func(name string) ([]byte, error) {
@ -20,7 +20,7 @@ func Migrate(db *sql.DB, customSteps []sqlite.PostStep) error {
} }
// MigrateTo is used for testing purposes // MigrateTo is used for testing purposes
func MigrateTo(db *sql.DB, customSteps []sqlite.PostStep, untilVersion uint) error { func MigrateTo(db *sql.DB, customSteps []*sqlite.PostStep, untilVersion uint) error {
return sqlite.Migrate(db, bindata.Resource( return sqlite.Migrate(db, bindata.Resource(
AssetNames(), AssetNames(),
func(name string) ([]byte, error) { func(name string) ([]byte, error) {

View File

@ -10,7 +10,7 @@ import (
// Migrate applies migrations. // Migrate applies migrations.
// see Migrate in vendor/status-go/sqlite/migrate.go // see Migrate in vendor/status-go/sqlite/migrate.go
func Migrate(db *sql.DB, customSteps []sqlite.PostStep) error { func Migrate(db *sql.DB, customSteps []*sqlite.PostStep) error {
return sqlite.Migrate(db, bindata.Resource( return sqlite.Migrate(db, bindata.Resource(
AssetNames(), AssetNames(),
func(name string) ([]byte, error) { func(name string) ([]byte, error) {

View File

@ -10,9 +10,11 @@ import (
bindata "github.com/status-im/migrate/v4/source/go_bindata" bindata "github.com/status-im/migrate/v4/source/go_bindata"
) )
type CustomMigrationFunc func(tx *sql.Tx) error
type PostStep struct { type PostStep struct {
Version uint Version uint
CustomMigration func(tx *sql.Tx) error CustomMigration CustomMigrationFunc
RollBackVersion uint RollBackVersion uint
} }
@ -36,7 +38,7 @@ var migrationTable = "status_go_" + sqlcipher.DefaultMigrationsTable
// //
// untilVersion, for testing purposes optional parameter, can be used to limit the migration to a specific version. // untilVersion, for testing purposes optional parameter, can be used to limit the migration to a specific version.
// Pass nil to migrate to the latest available version. // Pass nil to migrate to the latest available version.
func Migrate(db *sql.DB, resources *bindata.AssetSource, customSteps []PostStep, untilVersion *uint) error { func Migrate(db *sql.DB, resources *bindata.AssetSource, customSteps []*PostStep, untilVersion *uint) error {
source, err := bindata.WithInstance(resources) source, err := bindata.WithInstance(resources)
if err != nil { if err != nil {
return fmt.Errorf("failed to create bindata migration source: %w", err) return fmt.Errorf("failed to create bindata migration source: %w", err)
@ -82,7 +84,7 @@ func Migrate(db *sql.DB, resources *bindata.AssetSource, customSteps []PostStep,
// runCustomMigrations performs source migrations from current to each custom steps, then runs custom migration callback // runCustomMigrations performs source migrations from current to each custom steps, then runs custom migration callback
// until it executes all custom migrations or an error occurs and it tries to rollback to RollBackVersion if > 0. // until it executes all custom migrations or an error occurs and it tries to rollback to RollBackVersion if > 0.
func runCustomMigrations(m *migrate.Migrate, db *sql.DB, customSteps []PostStep, customIndex int, untilVersion *uint) error { func runCustomMigrations(m *migrate.Migrate, db *sql.DB, customSteps []*PostStep, customIndex int, untilVersion *uint) error {
for customIndex < len(customSteps) && (untilVersion == nil || customSteps[customIndex].Version <= *untilVersion) { for customIndex < len(customSteps) && (untilVersion == nil || customSteps[customIndex].Version <= *untilVersion) {
customStep := customSteps[customIndex] customStep := customSteps[customIndex]
@ -99,7 +101,8 @@ func runCustomMigrations(m *migrate.Migrate, db *sql.DB, customSteps []PostStep,
return nil return nil
} }
func runCustomMigrationStep(db *sql.DB, customStep PostStep, m *migrate.Migrate) error { func runCustomMigrationStep(db *sql.DB, customStep *PostStep, m *migrate.Migrate) error {
sqlTx, err := db.Begin() sqlTx, err := db.Begin()
if err != nil { if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err) return fmt.Errorf("failed to begin transaction: %w", err)
@ -116,7 +119,7 @@ func runCustomMigrationStep(db *sql.DB, customStep PostStep, m *migrate.Migrate)
return nil return nil
} }
func rollbackCustomMigration(m *migrate.Migrate, customStep PostStep, customErr error) error { func rollbackCustomMigration(m *migrate.Migrate, customStep *PostStep, customErr error) error {
if customStep.RollBackVersion > 0 { if customStep.RollBackVersion > 0 {
err := m.Migrate(customStep.RollBackVersion) err := m.Migrate(customStep.RollBackVersion)
newV, _, _ := m.Version() newV, _, _ := m.Version()
@ -135,7 +138,8 @@ func runRemainingMigrations(m *migrate.Migrate, untilVersion *uint) error {
} }
} else { } else {
if err := m.Up(); err != nil && err != migrate.ErrNoChange { if err := m.Up(); err != nil && err != migrate.ErrNoChange {
return fmt.Errorf("failed to migrate up: %w", err) ver, _, _ := m.Version()
return fmt.Errorf("failed to migrate up: %w, current version: %d", err, ver)
} }
} }
return nil return nil

View File

@ -27,6 +27,7 @@ const (
InMemoryPath = ":memory:" InMemoryPath = ":memory:"
V4CipherPageSize = 8192 V4CipherPageSize = 8192
V3CipherPageSize = 1024 V3CipherPageSize = 1024
sqlMainDatabase = "main"
) )
// DecryptDB completely removes the encryption from the db // DecryptDB completely removes the encryption from the db
@ -58,7 +59,22 @@ func encryptDB(db *sql.DB, encryptedPath string, key string, kdfIterationsNumber
defer onEnd() defer onEnd()
} }
_, err := db.Exec(`ATTACH DATABASE '` + encryptedPath + `' AS encrypted KEY '` + key + `'`) attachedDbName := "encrypted"
err := attachDatabaseWithDefaultSettings(db, encryptedPath, attachedDbName, key, kdfIterationsNumber)
if err != nil {
return err
}
_, err = db.Exec(fmt.Sprintf(`SELECT sqlcipher_export('%s')`, attachedDbName))
if err != nil {
return err
}
_, err = db.Exec(fmt.Sprintf(`DETACH DATABASE %s`, attachedDbName))
return err
}
func attachDatabaseWithDefaultSettings(db *sql.DB, attachedDbPath string, attachedDbName string, key string, kdfIterationsNumber int) error {
_, err := db.Exec(fmt.Sprintf(`ATTACH DATABASE '%s' AS %s KEY '%s'`, attachedDbPath, attachedDbName, key))
if err != nil { if err != nil {
return err return err
} }
@ -67,31 +83,39 @@ func encryptDB(db *sql.DB, encryptedPath string, key string, kdfIterationsNumber
kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber
} }
_, err = db.Exec(fmt.Sprintf("PRAGMA encrypted.kdf_iter = '%d'", kdfIterationsNumber)) if _, err := db.Exec(fmt.Sprintf(`PRAGMA %s.busy_timeout = 60000`, attachedDbName)); err != nil {
return errors.New("failed to set `busy_timeout` pragma on attached db")
}
return setDatabaseCipherSettings(db, kdfIterationsNumber, attachedDbName)
}
func setDatabaseCipherSettings(db *sql.DB, kdfIterationsNumber int, dbNameOpt ...string) error {
dbName := sqlMainDatabase
if len(dbNameOpt) > 0 {
dbName = dbNameOpt[0]
}
_, err := db.Exec(fmt.Sprintf("PRAGMA %s.kdf_iter = '%d'", dbName, kdfIterationsNumber))
if err != nil { if err != nil {
return err return err
} }
if _, err := db.Exec(fmt.Sprintf("PRAGMA encrypted.cipher_page_size = %d", V4CipherPageSize)); err != nil { if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_page_size = %d", dbName, V4CipherPageSize)); err != nil {
fmt.Println("failed to set cipher_page_size pragma") fmt.Println("failed to set cipher_page_size pragma")
return err return err
} }
if _, err := db.Exec("PRAGMA encrypted.cipher_hmac_algorithm = HMAC_SHA1"); err != nil { if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_hmac_algorithm = HMAC_SHA1", dbName)); err != nil {
fmt.Println("failed to set cipher_hmac_algorithm pragma") fmt.Println("failed to set cipher_hmac_algorithm pragma")
return err return err
} }
if _, err := db.Exec("PRAGMA encrypted.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1"); err != nil { if _, err := db.Exec(fmt.Sprintf("PRAGMA %s.cipher_kdf_algorithm = PBKDF2_HMAC_SHA1", dbName)); err != nil {
fmt.Println("failed to set cipher_kdf_algorithm pragma") fmt.Println("failed to set cipher_kdf_algorithm pragma")
return err return err
} }
_, err = db.Exec(`SELECT sqlcipher_export('encrypted')`) return nil
if err != nil {
return err
}
_, err = db.Exec(`DETACH DATABASE encrypted`)
return err
} }
// EncryptDB takes a plaintext database and adds encryption // EncryptDB takes a plaintext database and adds encryption
@ -140,7 +164,7 @@ func buildSqlcipherDSN(path string) (string, error) {
return path + queryOperator + "_txlock=immediate", nil return path + queryOperator + "_txlock=immediate", nil
} }
func openDB(path string, key string, kdfIterationsNumber int, chiperPageSize int) (*sql.DB, error) { func openDB(path string, key string, kdfIterationsNumber int, cipherPageSize int) (*sql.DB, error) {
driverName := fmt.Sprintf("sqlcipher_with_extensions-%d", len(sql.Drivers())) driverName := fmt.Sprintf("sqlcipher_with_extensions-%d", len(sql.Drivers()))
sql.Register(driverName, &sqlcipher.SQLiteDriver{ sql.Register(driverName, &sqlcipher.SQLiteDriver{
ConnectHook: func(conn *sqlcipher.SQLiteConn) error { ConnectHook: func(conn *sqlcipher.SQLiteConn) error {
@ -156,7 +180,7 @@ func openDB(path string, key string, kdfIterationsNumber int, chiperPageSize int
kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber kdfIterationsNumber = sqlite.ReducedKDFIterationsNumber
} }
if _, err := conn.Exec(fmt.Sprintf("PRAGMA cipher_page_size = %d", chiperPageSize), nil); err != nil { if _, err := conn.Exec(fmt.Sprintf("PRAGMA cipher_page_size = %d", cipherPageSize), nil); err != nil {
fmt.Println("failed to set cipher_page_size pragma") fmt.Println("failed to set cipher_page_size pragma")
return err return err
} }
@ -189,7 +213,6 @@ func openDB(path string, key string, kdfIterationsNumber int, chiperPageSize int
}) })
dsn, err := buildSqlcipherDSN(path) dsn, err := buildSqlcipherDSN(path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,7 +246,7 @@ func openDB(path string, key string, kdfIterationsNumber int, chiperPageSize int
return db, nil return db, nil
} }
// OpenDB opens not-encrypted database. // OpenDB opens encrypted database.
func OpenDB(path string, key string, kdfIterationsNumber int) (*sql.DB, error) { func OpenDB(path string, key string, kdfIterationsNumber int) (*sql.DB, error) {
return openDB(path, key, kdfIterationsNumber, V4CipherPageSize) return openDB(path, key, kdfIterationsNumber, V4CipherPageSize)
} }

View File

@ -14,8 +14,7 @@ func (a DbInitializer) Initialize(path, password string, kdfIterationsNumber int
return InitializeDB(path, password, kdfIterationsNumber) return InitializeDB(path, password, kdfIterationsNumber)
} }
var walletCustomSteps = []sqlite.PostStep{ var walletCustomSteps = []*sqlite.PostStep{}
}
func doMigration(db *sql.DB) error { func doMigration(db *sql.DB) error {
// Run all the new migrations // Run all the new migrations

View File

@ -10,7 +10,7 @@ import (
// Migrate applies migrations. // Migrate applies migrations.
// see Migrate in vendor/status-go/sqlite/migrate.go // see Migrate in vendor/status-go/sqlite/migrate.go
func Migrate(db *sql.DB, customSteps []sqlite.PostStep) error { func Migrate(db *sql.DB, customSteps []*sqlite.PostStep) error {
return sqlite.Migrate(db, bindata.Resource( return sqlite.Migrate(db, bindata.Resource(
AssetNames(), AssetNames(),
func(name string) ([]byte, error) { func(name string) ([]byte, error) {
@ -20,7 +20,7 @@ func Migrate(db *sql.DB, customSteps []sqlite.PostStep) error {
} }
// MigrateTo is used for testing purposes // MigrateTo is used for testing purposes
func MigrateTo(db *sql.DB, customSteps []sqlite.PostStep, untilVersion uint) error { func MigrateTo(db *sql.DB, customSteps []*sqlite.PostStep, untilVersion uint) error {
return sqlite.Migrate(db, bindata.Resource( return sqlite.Migrate(db, bindata.Resource(
AssetNames(), AssetNames(),
func(name string) ([]byte, error) { func(name string) ([]byte, error) {