/protocol/sqlite/db.go

https://github.com/status-im/status-go · Go · 159 lines · 109 code · 30 blank · 20 comment · 35 complexity · 0e6e1ba4cb90c3e39ab18ed93b9d4d64 MD5 · raw file

  1. package sqlite
  2. import (
  3. "database/sql"
  4. "fmt"
  5. "os"
  6. "github.com/pkg/errors"
  7. _ "github.com/mutecomm/go-sqlcipher" // We require go sqlcipher that overrides default implementation
  8. "github.com/status-im/migrate/v4"
  9. "github.com/status-im/migrate/v4/database/sqlcipher"
  10. bindata "github.com/status-im/migrate/v4/source/go_bindata"
  11. mvdsmigrations "github.com/vacp2p/mvds/persistenceutil"
  12. )
  13. // The default number of kdf iterations in sqlcipher (from version 3.0.0)
  14. // https://github.com/sqlcipher/sqlcipher/blob/fda4c68bb474da7e955be07a2b807bda1bb19bd2/CHANGELOG.md#300---2013-11-05
  15. // https://www.zetetic.net/sqlcipher/sqlcipher-api/#kdf_iter
  16. const defaultKdfIterationsNumber = 64000 // nolint: deadcode,varcheck,unused
  17. // The reduced number of kdf iterations (for performance reasons) which is
  18. // currently used for derivation of the database key
  19. // https://github.com/status-im/status-go/pull/1343
  20. // https://notes.status.im/i8Y_l7ccTiOYq09HVgoFwA
  21. const reducedKdfIterationsNumber = 3200
  22. const inMemoryPath = ":memory:"
  23. // MigrationConfig is a struct that allows to define bindata migrations.
  24. type MigrationConfig struct {
  25. AssetNames []string
  26. AssetGetter func(name string) ([]byte, error)
  27. }
  28. // Open opens or initializes a new database for a given file path.
  29. // MigrationConfig is optional but if provided migrations are applied automatically.
  30. func Open(path, key string) (*sql.DB, error) {
  31. return open(path, key, reducedKdfIterationsNumber)
  32. }
  33. // OpenInMemory opens an in memory SQLite database.
  34. // Number of KDF iterations is reduced to 0.
  35. func OpenInMemory() (*sql.DB, error) {
  36. return open(inMemoryPath, "", 0)
  37. }
  38. // OpenWithIter allows to open a new database with a custom number of kdf iterations.
  39. // Higher kdf iterations number makes it slower to open the database.
  40. func OpenWithIter(path, key string, kdfIter int) (*sql.DB, error) {
  41. return open(path, key, kdfIter)
  42. }
  43. func open(path string, key string, kdfIter int) (*sql.DB, error) {
  44. if path != inMemoryPath {
  45. _, err := os.OpenFile(path, os.O_CREATE, 0600)
  46. if err != nil {
  47. return nil, err
  48. }
  49. }
  50. db, err := sql.Open("sqlite3", path)
  51. if err != nil {
  52. return nil, err
  53. }
  54. keyString := fmt.Sprintf("PRAGMA key = '%s'", key)
  55. // Disable concurrent access as not supported by the driver
  56. db.SetMaxOpenConns(1)
  57. if _, err = db.Exec("PRAGMA foreign_keys=ON"); err != nil {
  58. return nil, err
  59. }
  60. if _, err = db.Exec(keyString); err != nil {
  61. return nil, err
  62. }
  63. kdfString := fmt.Sprintf("PRAGMA kdf_iter = '%d'", kdfIter)
  64. if _, err = db.Exec(kdfString); err != nil {
  65. return nil, err
  66. }
  67. if err := Migrate(db); err != nil {
  68. return nil, err
  69. }
  70. return db, nil
  71. }
  72. // ApplyMigrations allows to apply bindata migrations on the current *sql.DB.
  73. // `assetNames` is a list of assets with migrations and `assetGetter` is responsible
  74. // for returning the content of the asset with a given name.
  75. func ApplyMigrations(db *sql.DB, assetNames []string, assetGetter func(name string) ([]byte, error)) error {
  76. resources := bindata.Resource(
  77. assetNames,
  78. assetGetter,
  79. )
  80. source, err := bindata.WithInstance(resources)
  81. if err != nil {
  82. return errors.Wrap(err, "failed to create migration source")
  83. }
  84. driver, err := sqlcipher.WithInstance(db, &sqlcipher.Config{
  85. MigrationsTable: "status_protocol_go_" + sqlcipher.DefaultMigrationsTable,
  86. })
  87. if err != nil {
  88. return errors.Wrap(err, "failed to create driver")
  89. }
  90. m, err := migrate.NewWithInstance(
  91. "go-bindata",
  92. source,
  93. "sqlcipher",
  94. driver,
  95. )
  96. if err != nil {
  97. return errors.Wrap(err, "failed to create migration instance")
  98. }
  99. version, dirty, err := m.Version()
  100. if err != nil && err != migrate.ErrNilVersion {
  101. return errors.Wrap(err, "could not get version")
  102. }
  103. // Force version if dirty
  104. if dirty {
  105. if err = m.Force(int(version)); err != nil {
  106. return errors.Wrap(err, "failed to force migration")
  107. }
  108. }
  109. if err = m.Up(); err != migrate.ErrNoChange {
  110. return errors.Wrap(err, "failed to migrate")
  111. }
  112. return nil
  113. }
  114. func Migrate(database *sql.DB) error {
  115. // Apply migrations for all components.
  116. err := mvdsmigrations.Migrate(database)
  117. if err != nil {
  118. return errors.Wrap(err, "failed to apply mvds migrations")
  119. }
  120. migrationNames, migrationGetter, err := prepareMigrations(defaultMigrations)
  121. if err != nil {
  122. return errors.Wrap(err, "failed to prepare status-go/protocol migrations")
  123. }
  124. err = ApplyMigrations(database, migrationNames, migrationGetter)
  125. if err != nil {
  126. return errors.Wrap(err, "failed to apply status-go/protocol migrations")
  127. }
  128. return nil
  129. }