Skip to content

Commit

Permalink
Have sqlite also write to jdbc url file (#2153)
Browse files Browse the repository at this point in the history
Eclair will now refuse to start if the database config is changed from `sqlite` to `postgres` or the opposite.
  • Loading branch information
pm47 authored Jan 27, 2022
1 parent ffecd62 commit 1f6a7af
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 29 deletions.
48 changes: 28 additions & 20 deletions eclair-core/src/main/scala/fr/acinq/eclair/db/Databases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -73,15 +73,18 @@ object Databases extends Logging {
}

object SqliteDatabases {
def apply(auditJdbc: Connection, networkJdbc: Connection, eclairJdbc: Connection): SqliteDatabases = SqliteDatabases(
network = new SqliteNetworkDb(networkJdbc),
audit = new SqliteAuditDb(auditJdbc),
channels = new SqliteChannelsDb(eclairJdbc),
peers = new SqlitePeersDb(eclairJdbc),
payments = new SqlitePaymentsDb(eclairJdbc),
pendingCommands = new SqlitePendingCommandsDb(eclairJdbc),
backupConnection = eclairJdbc
)
def apply(auditJdbc: Connection, networkJdbc: Connection, eclairJdbc: Connection, jdbcUrlFile_opt: Option[File]): SqliteDatabases = {
jdbcUrlFile_opt.foreach(checkIfDatabaseUrlIsUnchanged("sqlite", _))
SqliteDatabases(
network = new SqliteNetworkDb(networkJdbc),
audit = new SqliteAuditDb(auditJdbc),
channels = new SqliteChannelsDb(eclairJdbc),
peers = new SqlitePeersDb(eclairJdbc),
payments = new SqlitePaymentsDb(eclairJdbc),
pendingCommands = new SqlitePendingCommandsDb(eclairJdbc),
backupConnection = eclairJdbc
)
}
}

case class PostgresDatabases private(network: PgNetworkDb,
Expand Down Expand Up @@ -241,19 +244,22 @@ object Databases extends Logging {

databases
}
}

private def checkIfDatabaseUrlIsUnchanged(url: String, urlFile: File): Unit = {
def readString(path: Path): String = Files.readAllLines(path).get(0)
/** We raise this exception when the jdbc url changes, to prevent using a different server involuntarily. */
case class JdbcUrlChanged(before: String, after: String) extends RuntimeException(s"The database URL has changed since the last start. It was `$before`, now it's `$after`. If this was intended, make sure you have migrated your data, otherwise your channels will be force-closed and you may lose funds.")

def writeString(path: Path, string: String): Unit = Files.write(path, java.util.Arrays.asList(string))
private def checkIfDatabaseUrlIsUnchanged(url: String, urlFile: File): Unit = {
def readString(path: Path): String = Files.readAllLines(path).get(0)

if (urlFile.exists()) {
val oldUrl = readString(urlFile.toPath)
if (oldUrl != url)
throw JdbcUrlChanged(oldUrl, url)
} else {
writeString(urlFile.toPath, url)
}
def writeString(path: Path, string: String): Unit = Files.write(path, java.util.Arrays.asList(string))

if (urlFile.exists()) {
val oldUrl = readString(urlFile.toPath)
if (oldUrl != url)
throw JdbcUrlChanged(oldUrl, url)
} else {
writeString(urlFile.toPath, url)
}
}

Expand All @@ -278,10 +284,12 @@ object Databases extends Logging {
*/
def sqlite(dbdir: File): SqliteDatabases = {
dbdir.mkdirs()
val jdbcUrlFile = new File(dbdir, "last_jdbcurl")
SqliteDatabases(
eclairJdbc = SqliteUtils.openSqliteFile(dbdir, "eclair.sqlite", exclusiveLock = true, journalMode = "wal", syncFlag = "full"), // there should only be one process writing to this file
networkJdbc = SqliteUtils.openSqliteFile(dbdir, "network.sqlite", exclusiveLock = false, journalMode = "wal", syncFlag = "normal"), // we don't need strong durability guarantees on the network db
auditJdbc = SqliteUtils.openSqliteFile(dbdir, "audit.sqlite", exclusiveLock = false, journalMode = "wal", syncFlag = "full")
auditJdbc = SqliteUtils.openSqliteFile(dbdir, "audit.sqlite", exclusiveLock = false, journalMode = "wal", syncFlag = "full"),
jdbcUrlFile_opt = Some(jdbcUrlFile)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,6 @@ import scala.concurrent.duration._

object PgUtils extends JdbcUtils {

/** We raise this exception when the jdbc url changes, to prevent using a different server involuntarily. */
case class JdbcUrlChanged(before: String, after: String) extends RuntimeException(s"The database URL has changed since the last start. It was `$before`, now it's `$after`")

sealed trait PgLock {
def obtainExclusiveLock(implicit ds: DataSource): Unit

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object TestDatabases {

def inMemoryDb(): Databases = {
val connection = sqliteInMemory()
val dbs = Databases.SqliteDatabases(connection, connection, connection)
val dbs = Databases.SqliteDatabases(connection, connection, connection, jdbcUrlFile_opt = None)
dbs.copy(channels = new SqliteChannelsDbWithValidation(dbs.channels))
}

Expand Down Expand Up @@ -89,7 +89,9 @@ object TestDatabases {
// @formatter:off
override val connection: SQLiteConnection = sqliteInMemory()
override lazy val db: Databases = {
val dbs = Databases.SqliteDatabases(connection, connection, connection)
val jdbcUrlFile: File = new File(TestUtils.BUILD_DIRECTORY, s"jdbcUrlFile_${UUID.randomUUID()}.tmp")
jdbcUrlFile.deleteOnExit()
val dbs = Databases.SqliteDatabases(connection, connection, connection, jdbcUrlFile_opt = Some(jdbcUrlFile))
dbs.copy(channels = new SqliteChannelsDbWithValidation(dbs.channels))
}
override def close(): Unit = ()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package fr.acinq.eclair.db

import com.opentable.db.postgres.embedded.EmbeddedPostgres
import com.typesafe.config.{Config, ConfigFactory, ConfigValue}
import com.typesafe.config.{Config, ConfigFactory}
import fr.acinq.eclair.db.Databases.JdbcUrlChanged
import fr.acinq.eclair.db.DbEventHandler.ChannelEvent
import fr.acinq.eclair.db.pg.PgUtils.ExtendedResultSet._
import fr.acinq.eclair.db.pg.PgUtils.PgLock.{LeaseLock, LockFailure, LockFailureHandler}
import fr.acinq.eclair.db.pg.PgUtils.{JdbcUrlChanged, migrateTable, using}
import fr.acinq.eclair.db.pg.PgUtils.{migrateTable, using}
import fr.acinq.eclair.payment.ChannelPaymentRelayed
import fr.acinq.eclair.router.Announcements
import fr.acinq.eclair.wire.internal.channel.ChannelCodecsSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@

package fr.acinq.eclair.db

import java.sql.SQLException
import fr.acinq.eclair.{TestConstants, TestDatabases}
import fr.acinq.eclair.db.Databases.JdbcUrlChanged
import fr.acinq.eclair.db.sqlite.SqliteUtils.using
import fr.acinq.eclair.{TestDatabases, TestUtils}
import org.scalatest.funsuite.AnyFunSuite

import java.io.File
import java.nio.file.Files
import java.sql.SQLException
import java.util.UUID

class SqliteUtilsSpec extends AnyFunSuite {

test("using with auto-commit disabled") {
Expand Down Expand Up @@ -74,4 +79,28 @@ class SqliteUtilsSpec extends AnyFunSuite {
}
}

test("jdbc url check") {
val datadir = new File(TestUtils.BUILD_DIRECTORY, s"sqlite_test_${UUID.randomUUID()}")
val jdbcUrlPath = new File(datadir, "last_jdbcurl")
datadir.mkdirs()

// first start : write to file
val db1 = Databases.sqlite(datadir)
db1.channels.close()

assert(Files.readString(jdbcUrlPath.toPath).trim == "sqlite")

// 2nd start : no-op
val db2 = Databases.sqlite(datadir)
db2.channels.close()

// we modify the file
Files.writeString(jdbcUrlPath.toPath, "postgres")

// boom
intercept[JdbcUrlChanged] {
Databases.sqlite(datadir)
}
}

}

0 comments on commit 1f6a7af

Please sign in to comment.